Search code examples
arraysalgorithmdynamic-programmingparity

Change the minimum number of entries in an array so that the sum of any k consecutive items is even


We are given an array of integers. We have to change the minimum number of those integers however we'd like so that, for some fixed parameter k, the sum of any k consecutive items in the array is even. Example:

N = 8; K = 3; A = {1,2,3,4,5,6,7,8}

We can change 3 elements (4th,5th,6th) so the array can be {1,2,3,5,6,7,7,8} then

  • 1+2+3=6 is even
  • 2+3+5=10 is even
  • 3+5+6=14 is even
  • 5+6+7=18 is even
  • 6+7+7=20 is even
  • 7+7+8=22 is even

Solution

  • There's a very nice O(n)-time solution to this problem that, at a high level, works like this:

    1. Recognize that determining which items to flip boils down to determining a pattern that repeats across the array of which items to flip.
    2. Use dynamic programming to determine what that pattern is.

    Here's how to arrive at this solution.

    First, some observations. Since all we care about here is whether the sums are even or odd, we actually don't care about the numbers' exact values. We just care about whether they're even or odd. So let's begin by replacing each number with either 0 (if the number is even) or 1 (if it's odd). Now, our task is to make each window of k elements have an even number of 1s.

    Second, the pattern of 0s and 1s that results after you've transformed the array has a surprising shape: it's simply a repeated copy of the first k elements of the array. For example, suppose k = 5 and we decide that the array should start off as 1 0 1 1 1. What must the sixth array element be? Well, in moving from the first window to the second, we dropped a 1 off the front of the window, changing the parity to odd. We therefore have to have the next array element be a 1, which means that the sixth array element must be a 1, equal to the first array element. The seventh array element then has to be a 0, since in moving from the second window to the third we drop off a zero. This process means that whatever we decide on for the first k elements turns out to determine the entire final sequence of values.

    This means that we can reframe the problem in the following way: break the original input array of n items into n/k blocks of size k. We're now asked to pick a sequence of 0s and 1s such that

    • this sequence differs in as few places as possible from the n/k blocks of k items each, and
    • the sequence has an even number of 1s.

    For example, given the input sequence

    0 1 1 0 1 1 1 0 0 1 0 1 1 1 0 1 1 1
    

    and k = 3, we would form the blocks

    0 1 1, 0 1 1, 1 0 0, 1 0 1, 1 1 0, 1 1 1
    

    and then try to find a pattern of length three with an even number of 1s in it such that replacing each block with that pattern requires the fewest number of edits.

    Let's see how to take that problem on. Let's work one bit at a time. For example, we can ask: what's the cost of making the first bit a 0? What's the cost of making the first bit a 1? The cost of making the first bit a 0 is equal to the number of blocks that have a 1 at the front, and the cost of making the first bit a 1 is equal to the number of blocks that have a 0 at the front. We can work out the cost of setting each bit, individually, to either to zero or to one. That gives us a matrix like this one:

                           Bit #0   Bit #1   Bit #2   Bit #3     ...   Bit #k-1
    ---------------------+--------+--------+--------+--------+--------+----------
    Cost of setting to 0 |        |        |        |        |        |        |
    Cost of setting to 1 |        |        |        |        |        |        |
    

    We now need to choose a value for each column with the goal of minimizing the total cost picked, subject to the constraint that we pick an even number of bits to be equal to 1. And this is a nice dynamic programming exercise. We consider subproblems of the form

    What is the lowest cost you can make out of the first m columns from the table, provided your choice has parity p of items chosen from the bottom row?

    We can store this in an (k + 1) × 2 table T[m][p], where, for example, T[3][even] is the lowest cost you can achieve using the first three columns with an even number of items set to 1, and T[6][odd] is the lowest cost you can achieve using the first six columns with an odd number of items set to 1. This gives the following recurrence:

    • T[0][even] = 0 (using zero columns costs nothing)
    • T[0][odd] = ∞ (you cannot have an odd number of bits set to 1 if you use no colums)
    • T[m+1][p] = min(T[m][p] + cost of setting this bit to 0, T[m][!p] + cost of setting this bit to 1) (either use a zero and keep the same parity, or use a 1 and flip the parity).

    This can be evaluated in time O(k), and the resulting minimum cost is given by T[n][even]. You can use a standard DP table walk to reconstruct the optimal solution from this point.

    Overall, here's the final algorithm:

    create a table costs[k+1][2], all initially zero.
    
    /* Populate the costs table. costs[m][0] is the cost of setting bit m
     * to 0; costs[m][1] is the cost of setting bit m to 1. We work this
     * out by breaking the input into blocks of size k, then seeing, for
     * each item within each block, what its parity is. The cost of setting
     * that bit to the other parity then increases by one.
     */
    for i = 0 to n - 1:
        parity = array[i] % 2
        costs[i % k][!parity]++ // Cost of changing this entry
    
    /* Do the DP algorithm to find the minimum cost. */
    create array T[k + 1][2]
    T[0][0] = 0
    T[0][1] = infinity
    
    for m from 1 to k:
        for p from 0 to 1:
            T[m][p] = min(T[m - 1][p]  + costs[m - 1][0],
                          T[m - 1][!p] + costs[m - 1][1])
    
    return T[m][0]
    

    Overall, we do O(n) work with our initial pass to work out the costs of setting each bit, independently, to 0. We then do O(k) work with the DP step at the end. The overall work is therefore O(n + k), and assuming k ≤ n (otherwise the problem is trivial) the cost is O(n).