Max Consecutive Ones III

Initial Approach

Declare a counter to keep track of maximum number of consecutive ones Initialize zero count to k Traverse the input array Keep incrementing the counter as long as 1 is the element in the array If there is a 0, decrement the zero count If the zero count becomes -1, reset it to k and reset ones count to 0 Update the maximum we have found so far as we traverse through the array

Mistakes

  • Missed to identify the invariant
  • Missed the subarray size formula. This can only be found at coding stage. Not at high level outline of the solution.

Brute-Force Solution

The brute-force solution would involve generating all possible subarrays, flipping at most k zeroes to ones in each, and then checking the length of consecutive ones. Here’s how you could do it:

  1. Loop through the array to find all subarrays.
  2. For each subarray, count the number of zeroes and ones.
  3. If the number of zeroes is less than or equal to k, find the length of the subarray.
  4. Keep track of the maximum length of such subarrays.

This would involve nested loops and would be highly inefficient. Specifically, the time complexity would be (O(n^2)), where (n) is the length of the array.

Here’s some pseudocode for a brute-force solution:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
max_length = 0

for i in range(len(nums)):
    for j in range(i, len(nums)):
        zero_count = 0
        one_count = 0
        for x in range(i, j+1):
            if nums[x] == 0:
                zero_count += 1
            else:
                one_count += 1

        if zero_count <= k:
            max_length = max(max_length, one_count + k)

return min(max_length, len(nums))

The above algorithm doesn’t actually flip the zeroes to ones but counts them to see if flipping would be possible within the given constraint of at most k flips. Also, the max_length is limited by the total length of the array.

Unnecessary Work

The unnecessary repeated work in the brute-force approach occurs mainly in two areas:

  1. Subarray Enumeration: For every subarray starting at index i and ending at index j, the algorithm goes through the elements to count the zeros and ones. This means the same elements get recounted multiple times for overlapping subarrays.

  2. Zero and One Counting: The number of zeros and ones within any given subarray is recalculated from scratch each time, even though this information could be incrementally updated from a previously calculated smaller subarray.

For example, if you’ve already calculated the number of zeros and ones for the subarray [i, j], you don’t need to recalculate everything for [i, j+1]. You can simply look at the new element nums[j+1] and update your counts accordingly.

Both of these issues lead to a time complexity of (O(n^2)) because you are looping through the array multiple times for each subarray.

Optimized approaches would try to eliminate this redundant work to improve efficiency.

Better Approach

This problem is about finding the maximum length of a subarray with consecutive 1’s, where you are allowed to flip at most k zeros to 1’s.

You are given a binary array nums and an integer k. The task is to find the maximum number of consecutive 1’s in the array if you can flip at most k 0’s.

In this problem, the invariant is the number of zeroes you are allowed to flip (k). While iterating through the array, you can count the zeroes encountered and compare it to k. The number of zeroes should never exceed k for any subarray that you are considering as a valid solution.

You can solve this problem using the sliding window technique:

  1. Initialize two pointers, left and right, to 0.
  2. Initialize max_length to 0 and zero_count to 0.
  3. Iterate right from 0 to length of array:
    • If nums[right] is 0, increment zero_count.
    • While zero_count > k, if nums[left] is 0, decrement zero_count. Move left one step to the right.
    • Update max_length as max(max_length, right - left + 1).

By keeping zero_count <= k, you ensure the invariant holds.

In the context of this problem, the loop invariant is the condition that the zero_count must not exceed k for any valid subarray being considered.

Specifically, during each iteration of the loop:

  1. zero_count keeps track of the number of zeros in the current subarray defined by [left, right].
  2. The while-loop inside ensures that zero_count is always less than or equal to k.

This invariant helps us maintain a valid subarray [left, right] where at most k zeroes can be flipped to ones, allowing us to find the maximum length of such a subarray.

In this problem, the invariant and the loop invariant are different but related.

  • Invariant: The more general rule for the problem is that in any valid subarray, you can have at most k zeroes that can be flipped to ones. This is a condition that your final answer must satisfy but is not necessarily maintained throughout each iteration of your algorithm.

  • Loop Invariant: During each iteration of the loop, the condition maintained is that zero_count <= k. This is checked and enforced during each cycle of the loop to ensure that the subarray [left, right] is valid according to the problem’s constraints.

So, while the invariant describes a condition that the solution must meet, the loop invariant is a condition maintained throughout the loop to ensure that you reach the correct solution.

In the sliding window algorithm, the loop invariant is: “The subarray between left and right contains at most k zeros.”

The algorithm maintains this invariant through each iteration of the loop:

  1. Initialization: At the start, left and right are both 0, and zero_count is 0. The subarray between left and right is empty, so it trivially contains at most k zeros. The invariant holds.

  2. Maintenance: On each iteration, you move the right pointer one step to the right. If you encounter a zero, you increment zero_count. If zero_count exceeds k, you start moving the left pointer to the right until zero_count is back to at most k. This ensures the loop invariant is maintained.

  3. Termination: When the loop ends, you’re guaranteed that the longest subarray you’ve found (stored in max_length) is the longest subarray containing at most k zeros. The invariant helps you confidently state this.

So, by maintaining the loop invariant, the algorithm ensures that at each step, the subarray between left and right is a valid candidate for a solution to the problem. This allows you to efficiently keep track of the maximum length of such valid subarrays.

Solution

A sliding window approach can be used to solve this problem. You can maintain a window within the array, and if you find a 0, increment a counter. If the counter exceeds k, shrink the window from the left. The maximum length of the window during this process will be your answer.

Here is the code in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Solution:
    def longestOnes(self, nums: List[int], k: int) -> int:
        left = 0
        max_length = 0
        zero_count = 0

        for right in range(len(nums)):
            if nums[right] == 0:
                zero_count += 1

            while zero_count > k:
                if nums[left] == 0:
                    zero_count -= 1
                left += 1

            max_length = max(max_length, right - left + 1)

        return max_length

Explanation

  • We use two pointers left and right to represent the current window within the array.
  • We iterate through the array using right pointer and whenever we find a 0, we increment the zero_count.
  • If zero_count exceeds k, we shrink the window from the left by incrementing the left pointer until the zero_count becomes equal to k.
  • At each step, we update max_length with the current window size if it’s greater than the current max_length.
  • Finally, we return the max_length, which is the maximum length of consecutive 1’s that can be achieved by flipping at most k zeros.

This code runs in O(n) time complexity and O(1) space complexity, where n is the length of the nums array.

title: Max Consecutive Ones III excerpt: This covers using the basic building blocks such as counter to maintain the constraint two pointers and sliding window. tags: counter-to-maintain-constraint two-pointers sliding-window

Given a binary array nums and an integer k, return the maximum number of consecutive 1’s in the array if you can flip at most k 0’s.

Example 1:

Input: nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2
Output: 6
Explanation: [1,1,1,0,0,1,1,1,1,1,1]
Bolded numbers were flipped from 0 to 1. The longest subarray is underlined.
Example 2:

Input: nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], k = 3
Output: 10
Explanation: [0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
Bolded numbers were flipped from 0 to 1. The longest subarray is underlined.

Constraints

  • 1 <= nums.length <= 105
  • nums[i] is either 0 or 1.
  • 0 <= k <= nums.length

Solution Outline

Consider the example:

 0 1 2 3 4 5 6 7 8 9 10
[1,1,1,0,0,0,1,1,1,1,0]
         i
                   j
longest = 5

i = 0
j = 4

j - i + 1 = longest (you cannot exceed k number of 0s)

When j moves to 5, we violate the constraint. What should we do to make the 0s less than or equal to k? Increment the i index. Until we get to a location where i and j only have at most k 0s. So stop at i = 4

What should we do next? We increment j until we only have max 1s and 0s < k. The index j stops at 9.

9 - 4 + 1 = 6

So, longest = 6

We now have a new longest value, we update the longest variable to 6. What should we do next? The reason we increase i is to drop off a zero (we are trying maintain the constraint). The reason we increment j is to see if we can extend the next element into our current subarray size. We increment j. We have a 0, which means number of 0s exceed k j has reached the last index, we can terminate. Return longest as the result.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def longest_ones(a, k)
  left = 0

  for right in (0..a.size-1) 
    # maintain the constraint
    if a[right] == 0
      k -= 1
    end

    if k < 0
      # If the element leaving the window is 0, increment k by one
      if a[left] == 0
        k += 1
      end

      left += 1
    end
  end
  
  right - left + 1
end

Building Blocks

  • Counter to Maintain the Constraint
  • Two Pointers