Facebook Pixel

1004. Max Consecutive Ones III

Problem Description

You are given a binary array nums containing only 0s and 1s, and an integer k. You can flip at most k zeros to ones. Your task is to find the maximum number of consecutive 1s you can get in the array after performing at most k flips.

For example, if nums = [1,1,0,0,1,1,0,1,1,1] and k = 2, you can flip the two 0s at positions 2 and 3 (or positions 2 and 6, or positions 3 and 6) to get consecutive 1s. The maximum consecutive 1s you can achieve is 7 by flipping the 0s at positions 2 and 6, resulting in [1,1,1,1,1,1,1,1,1,1] from index 1 to 7.

The solution uses a sliding window approach where:

  • The window expands as we iterate through the array
  • We track the count of 0s in the current window using cnt (calculated with XOR operation: x ^ 1 gives 1 when x is 0, and 0 when x is 1)
  • When the count of 0s exceeds k, we shrink the window from the left by moving the left pointer
  • The final window size represents the maximum consecutive 1s achievable

The key insight is that we don't need to shrink the window aggressively - we only move the left boundary by one position when needed, maintaining the maximum window size found so far. This is because we're interested in the maximum length, so once we find a valid window of a certain size, we never need to consider smaller windows.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The problem essentially asks: "What's the longest subarray containing at most k zeros?" If we can find such a subarray, we know that by flipping all zeros in it to ones, we get a consecutive sequence of 1s.

This naturally leads us to think about the sliding window technique. We can maintain a window that represents a valid subarray (containing at most k zeros) and try to expand it as much as possible.

The key insight is recognizing that we're looking for the maximum length window. Once we find a valid window of size n, there's no point in considering smaller windows anymore. This means:

  1. We always try to expand the window by moving the right pointer
  2. When the window becomes invalid (more than k zeros), instead of shrinking it back to find the next valid window, we just slide it forward by moving both pointers

Think of it like a rubber band that can only stretch, never shrink. As we scan through the array:

  • If adding a new element keeps the window valid (≤ k zeros), the window stretches
  • If adding a new element makes it invalid (> k zeros), the window slides forward maintaining its size

By the end of the array traversal, the window size represents the maximum consecutive 1s we can achieve. The actual position of the window doesn't matter - we only care about its maximum size.

The XOR trick (x ^ 1) is just a clever way to count zeros: it converts 0→1 and 1→0, so cnt += x ^ 1 increments the counter when we see a 0.

Learn more about Binary Search, Prefix Sum and Sliding Window patterns.

Solution Approach

We implement a sliding window algorithm that maintains the maximum valid window size throughout the traversal:

Variables:

  • l: Left pointer of the sliding window, initially at position 0
  • cnt: Counter for the number of zeros in the current window
  • The right pointer is implicit - it's our loop variable as we iterate through the array

Algorithm Steps:

  1. Iterate through the array from left to right (this represents the right boundary of our window expanding):

    for x in nums:
  2. Update zero count when we include a new element in the window:

    cnt += x ^ 1

    The XOR operation x ^ 1 returns 1 if x is 0, and 0 if x is 1, effectively counting zeros.

  3. Check if window is invalid (contains more than k zeros):

    if cnt > k:
  4. Slide the window forward by moving the left boundary right by one position:

    cnt -= nums[l] ^ 1  # Remove the leftmost element's contribution
    l += 1              # Move left pointer forward

    Note that we only move the left pointer by one position, not multiple. This maintains the maximum window size found so far.

  5. Calculate the result:

    return len(nums) - l

    Since we've processed all elements (right pointer at the end), the window size is len(nums) - l.

Why this works:

The algorithm maintains an important invariant: the window size never decreases. When we find a valid window of size w, we never shrink below this size. If adding a new element creates an invalid window, we slide the entire window forward (move both left and right pointers by 1), keeping the size constant. This ensures that by the end, we have the maximum possible window size.

Time Complexity: O(n) - we traverse the array once
Space Complexity: O(1) - only using a few variables

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Example: nums = [1,0,0,1,0,1], k = 2

We'll track the window with left pointer l, and count zeros with cnt. The right pointer moves as we iterate.

Initial state: l = 0, cnt = 0

Step 1: Process index 0 (value = 1)

  • cnt += 1 ^ 1 = 0 (no zeros added)
  • Window: [1], zeros count: 0 ≤ 2 ✓
  • l = 0, window size = 1

Step 2: Process index 1 (value = 0)

  • cnt += 0 ^ 1 = 1 (found a zero)
  • Window: [1,0], zeros count: 1 ≤ 2 ✓
  • l = 0, window size = 2

Step 3: Process index 2 (value = 0)

  • cnt += 0 ^ 1 = 1, so cnt = 2
  • Window: [1,0,0], zeros count: 2 ≤ 2 ✓
  • l = 0, window size = 3

Step 4: Process index 3 (value = 1)

  • cnt += 1 ^ 1 = 0
  • Window: [1,0,0,1], zeros count: 2 ≤ 2 ✓
  • l = 0, window size = 4

Step 5: Process index 4 (value = 0)

  • cnt += 0 ^ 1 = 1, so cnt = 3
  • Window would have 3 zeros > 2 ✗
  • Need to slide:
    • Remove nums[0] = 1 from count: cnt -= 1 ^ 1 = 0
    • Move left: l = 1
  • New window: [0,0,1,0], zeros count: 3 (still invalid!)

Step 6: Process index 5 (value = 1)

  • cnt += 1 ^ 1 = 0
  • Window: [0,0,1,0,1], zeros count: 3 > 2 ✗
  • Need to slide:
    • Remove nums[1] = 0 from count: cnt -= 0 ^ 1 = 1, so cnt = 2
    • Move left: l = 2
  • Final window: [0,1,0,1], zeros count: 2 ≤ 2 ✓

Result: len(nums) - l = 6 - 2 = 4

This means we can achieve a maximum of 4 consecutive 1s. Looking at the final window [0,1,0,1] at indices 2-5, if we flip the two zeros, we get [1,1,1,1] - indeed 4 consecutive ones!

Solution Implementation

1class Solution:
2    def longestOnes(self, nums: List[int], k: int) -> int:
3        # Initialize left pointer of sliding window and count of zeros in window
4        left = 0
5        zero_count = 0
6      
7        # Iterate through array with implicit right pointer (index)
8        for right in range(len(nums)):
9            # If current element is 0, increment zero count
10            # (x ^ 1 converts 0->1 and 1->0, so we count zeros)
11            if nums[right] == 0:
12                zero_count += 1
13          
14            # If we have more than k zeros, shrink window from left
15            if zero_count > k:
16                # If the left element being removed is 0, decrement zero count
17                if nums[left] == 0:
18                    zero_count -= 1
19                # Move left pointer forward
20                left += 1
21      
22        # The window size at the end is the maximum valid window
23        # (right pointer is at len(nums)-1, so window size is len(nums) - left)
24        return len(nums) - left
25
1class Solution {
2    public int longestOnes(int[] nums, int k) {
3        int left = 0;           // Left pointer of the sliding window
4        int zeroCount = 0;      // Count of zeros in the current window
5      
6        // Iterate through the array with an implicit right pointer
7        for (int num : nums) {
8            // If current element is 0, increment zero count (num ^ 1 equals 1 when num is 0)
9            zeroCount += num ^ 1;
10          
11            // If we have more than k zeros, shrink window from left
12            if (zeroCount > k) {
13                // If the left element being removed is 0, decrement zero count
14                zeroCount -= nums[left] ^ 1;
15                left++;  // Move left pointer to shrink the window
16            }
17        }
18      
19        // The maximum window size is the array length minus the final left position
20        // This works because the window only shrinks when necessary, maintaining max size
21        return nums.length - left;
22    }
23}
24
1class Solution {
2public:
3    int longestOnes(vector<int>& nums, int k) {
4        // Left pointer of the sliding window
5        int left = 0;
6      
7        // Count of zeros in the current window
8        int zeroCount = 0;
9      
10        // Iterate through the array with the right pointer (implicit)
11        for (int num : nums) {
12            // If current element is 0, increment zero count
13            // XOR with 1 converts: 0 -> 1, 1 -> 0
14            zeroCount += num ^ 1;
15          
16            // If we have more zeros than allowed flips
17            if (zeroCount > k) {
18                // Remove the leftmost element from window
19                // If it was a zero, decrement zero count
20                zeroCount -= nums[left] ^ 1;
21                // Move left pointer forward
22                left++;
23            }
24        }
25      
26        // The window size at the end is the maximum length
27        // Window size = total array size - left pointer position
28        return nums.size() - left;
29    }
30};
31
1/**
2 * Finds the maximum number of consecutive 1s that can be obtained by flipping at most k 0s.
3 * Uses a sliding window approach to track the valid window containing at most k zeros.
4 * 
5 * @param nums - Binary array containing only 0s and 1s
6 * @param k - Maximum number of 0s that can be flipped to 1s
7 * @returns Maximum length of consecutive 1s after flipping at most k zeros
8 */
9function longestOnes(nums: number[], k: number): number {
10    // Left pointer of the sliding window
11    let left: number = 0;
12  
13    // Count of zeros in the current window
14    let zeroCount: number = 0;
15  
16    // Iterate through the array with the right pointer
17    for (const num of nums) {
18        // If current element is 0 (num XOR 1 equals 1), increment zero count
19        zeroCount += num ^ 1;
20      
21        // If zero count exceeds k, shrink window from left
22        if (zeroCount > k) {
23            // If the left element being removed is 0, decrement zero count
24            zeroCount -= nums[left] ^ 1;
25            // Move left pointer forward
26            left++;
27        }
28    }
29  
30    // The window size is maintained at maximum valid length
31    // Final window size = total length - final left position
32    return nums.length - left;
33}
34

Time and Space Complexity

Time Complexity: O(n) where n is the length of the input array nums. The algorithm uses a single pass through the array with a for loop. Even though there's a conditional increment of the left pointer l inside the loop, each element is visited at most twice (once by the right pointer implicitly through the for loop, and potentially once when the left pointer moves). This results in a linear time complexity.

Space Complexity: O(1). The algorithm only uses a constant amount of extra space for the variables l (left pointer), cnt (count of zeros in the current window), and x (loop variable). No additional data structures that scale with the input size are used, making the space complexity constant.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Misunderstanding the Window Maintenance Strategy

Pitfall: Many developers initially try to aggressively shrink the window whenever it becomes invalid (contains more than k zeros), using a while loop:

# Incorrect aggressive shrinking
while zero_count > k:
    if nums[left] == 0:
        zero_count -= 1
    left += 1

This approach seems logical but actually prevents us from maintaining the maximum window size found so far. It causes the window to shrink unnecessarily, losing track of the maximum achievable length.

Solution: Use a single if statement instead of while. This maintains the window at its maximum size:

# Correct: maintain maximum window size
if zero_count > k:
    if nums[left] == 0:
        zero_count -= 1
    left += 1

2. Confusion with XOR Operation

Pitfall: Using x ^ 1 might be confusing and error-prone. Developers might mistakenly think it counts 1s instead of 0s, or forget how XOR works with binary values.

Solution: Use explicit comparison for clarity:

# Instead of: zero_count += nums[right] ^ 1
# Use this for better readability:
if nums[right] == 0:
    zero_count += 1

3. Incorrect Final Result Calculation

Pitfall: Returning right - left + 1 at the end of the loop, thinking we need to calculate the window size using both pointers:

# Incorrect if using range-based loop
for right in range(len(nums)):
    # ... window logic ...
return right - left + 1  # Wrong! right is len(nums)-1 at the end

Solution: Remember that after the loop completes, the window extends from left to the end of the array. The correct calculation is:

return len(nums) - left

4. Off-by-One Errors with Index Management

Pitfall: Mixing up whether to increment the left pointer before or after updating the zero count, leading to incorrect window management:

# Wrong order - incrementing before updating count
if zero_count > k:
    left += 1  # Wrong: should update count first
    if nums[left] == 0:  # This now checks the wrong element
        zero_count -= 1

Solution: Always update the count for the element being removed BEFORE moving the pointer:

if zero_count > k:
    if nums[left] == 0:  # Check element at current left position
        zero_count -= 1
    left += 1  # Then move the pointer
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More