Facebook Pixel

487. Max Consecutive Ones II 🔒

Problem Description

You are given a binary array nums that contains only 0s and 1s. You need to find the maximum number of consecutive 1s that you can get if you are allowed to flip at most one 0 to 1.

For example, if you have the array [1, 0, 1, 1, 0], you can flip the first 0 to get [1, 1, 1, 1, 0], which gives you 4 consecutive 1s. Or you could flip the last 0 to get [1, 0, 1, 1, 1], which gives you 3 consecutive 1s. The maximum would be 4.

The key constraint is that you can flip at most one 0, meaning you can either flip exactly one 0 or flip no 0s at all. Your goal is to find the longest possible sequence of consecutive 1s after performing this operation optimally.

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

Intuition

The problem essentially asks us to find the longest subarray that contains at most one 0. We can think of this as a sliding window problem where we maintain a window that satisfies our constraint.

The key insight is that we're looking for a contiguous subarray (window) where we have at most one 0. As we expand our window by moving through the array, we keep track of how many 0s are in our current window. When we encounter more than one 0, we need to shrink our window from the left until we're back to having at most one 0.

What makes this solution elegant is the observation that we don't actually need to shrink the window in the traditional sense. Since we're looking for the maximum length, once we've found a window of a certain size, we never need to consider smaller windows. So instead of shrinking the window when we have too many 0s, we can just slide it forward by moving both left and right boundaries together.

Think of it like this: if we've already found a valid window of size 5, there's no point in looking at windows of size 4 or smaller. So when we encounter a violation (more than one 0), we just shift the entire window forward by one position. This maintains the window size while exploring new possibilities. The window will only grow when we find a longer valid sequence.

The expression x ^ 1 is used to flip bits: if x is 1, x ^ 1 gives 0; if x is 0, x ^ 1 gives 1. So cnt += x ^ 1 increments the counter when we see a 0, and cnt -= nums[l] ^ 1 decrements it when we remove a 0 from the left of the window.

Solution Approach

We use a sliding window technique with a two-pointer approach. The algorithm maintains a window that contains at most one 0.

Here's how the implementation works:

  1. Initialize variables: We set l = 0 as the left boundary of our window and cnt = 0 to track the number of 0s in our current window.

  2. Iterate through the array: For each element x in nums:

    • We use cnt += x ^ 1 to update our 0 count. The XOR operation x ^ 1 flips the bit: if x is 0, it becomes 1 (incrementing our count), and if x is 1, it becomes 0 (no increment).
  3. Handle window constraint: When cnt > 1 (more than one 0 in the window):

    • We move the left boundary right by one position: l += 1
    • We update the count by removing the contribution of the element that just left the window: cnt -= nums[l] ^ 1
    • This effectively slides the window forward while maintaining its size
  4. Calculate result: The final answer is len(nums) - l, which represents the size of the largest valid window we found.

The key optimization here is that we don't shrink the window when we encounter a violation. Instead, we slide it forward maintaining its current size. This works because:

  • If we have a valid window of size k, we're not interested in smaller windows
  • The window only grows when we can extend it without violating the constraint
  • By the end, l tells us how much we had to shift from the beginning, so len(nums) - l gives us the maximum window size

This approach runs in O(n) time with O(1) space, making it very efficient.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through the algorithm with the array nums = [1, 0, 1, 1, 0].

Initial State:

  • l = 0 (left boundary)
  • cnt = 0 (count of zeros in window)
  • Window: empty

Step 1: Process index 0, value = 1

  • cnt += 1 ^ 1 = 0 (no increment since it's a 1)
  • cnt = 0 (still ≤ 1, so window is valid)
  • Window: [1], from index 0 to 0

Step 2: Process index 1, value = 0

  • cnt += 0 ^ 1 = 1 (increment since it's a 0)
  • cnt = 1 (still ≤ 1, so window is valid)
  • Window: [1, 0], from index 0 to 1

Step 3: Process index 2, value = 1

  • cnt += 1 ^ 1 = 0 (no increment since it's a 1)
  • cnt = 1 (still ≤ 1, so window is valid)
  • Window: [1, 0, 1], from index 0 to 2

Step 4: Process index 3, value = 1

  • cnt += 1 ^ 1 = 0 (no increment since it's a 1)
  • cnt = 1 (still ≤ 1, so window is valid)
  • Window: [1, 0, 1, 1], from index 0 to 3

Step 5: Process index 4, value = 0

  • cnt += 0 ^ 1 = 1 (increment since it's a 0)
  • cnt = 2 (now > 1, need to adjust!)
  • Since cnt > 1, we slide the window:
    • cnt -= nums[0] ^ 1 = nums[0] ^ 1 = 1 ^ 1 = 0 (remove contribution of nums[0])
    • l = 1 (move left boundary)
    • cnt = 2 (still > 1, need another adjustment!)
  • Since cnt > 1 again:
    • cnt -= nums[1] ^ 1 = nums[1] ^ 1 = 0 ^ 1 = 1 (remove contribution of nums[1])
    • l = 2 (move left boundary)
    • cnt = 1 (now valid)
  • Window: [1, 1, 0], from index 2 to 4

Final Result:

  • l = 2
  • Maximum length = len(nums) - l = 5 - 2 = 3

Wait, this gives us 3, but we should get 4. Let me reconsider the algorithm logic.

Actually, I need to correct my understanding. The algorithm slides the window by exactly one position when we exceed the constraint:

Step 5 Corrected: Process index 4, value = 0

  • cnt += 0 ^ 1 = 1 (increment since it's a 0)
  • cnt = 2 (now > 1, need to adjust!)
  • Since cnt > 1:
    • cnt -= nums[l] ^ 1 = nums[0] ^ 1 = 1 ^ 1 = 0 (no decrement, nums[0] was a 1)
    • l += 1 (move left boundary from 0 to 1)
    • cnt remains 2 (still > 1)
  • Since cnt > 1 still:
    • cnt -= nums[l] ^ 1 = nums[1] ^ 1 = 0 ^ 1 = 1 (decrement, nums[1] was a 0)
    • l += 1 (move left boundary from 1 to 2)
    • cnt = 1 (now valid)

Actually, let me reconsider the algorithm flow. The adjustment happens within the loop iteration:

Step 5 Refined: Process index 4, value = 0

  • cnt += 0 ^ 1 = 1 (increment since it's a 0)
  • cnt = 2 (now > 1)
  • While cnt > 1:
    • cnt -= nums[l] ^ 1 = nums[0] ^ 1 = 1 ^ 1 = 0
    • l = 1
    • cnt is still 2, so continue
    • cnt -= nums[l] ^ 1 = nums[1] ^ 1 = 0 ^ 1 = 1
    • l = 2
    • cnt = 1 (now valid, exit while loop)

Actually, reviewing the original solution more carefully, it seems the adjustment only happens once per iteration. Let me trace through more carefully:

Correct Walkthrough:

The key is that when cnt > 1, we slide the window forward by one position only.

  • i=0: x=1, cnt = 0, window=[1]
  • i=1: x=0, cnt = 1, window=[1,0]
  • i=2: x=1, cnt = 1, window=[1,0,1]
  • i=3: x=1, cnt = 1, window=[1,0,1,1]
  • i=4: x=0, cnt = 2 (too many zeros!)
    • Slide window: cnt -= nums[0]^1 = 0, l = 1
    • Now cnt = 2, but we only slide once per iteration
    • Window effectively becomes [0,1,1,0]

The final answer is 5 - 1 = 4, which matches our expected result. The window [0,1,1,0] can have its 0 flipped to get [1,1,1,1], giving us 4 consecutive 1s.

Solution Implementation

1class Solution:
2    def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
3        # Initialize left pointer and zero counter
4        left = 0
5        zero_count = 0
6      
7        # Iterate through each element in the array
8        for num in nums:
9            # If current element is 0, increment zero counter
10            # (num ^ 1 converts 0 to 1 and 1 to 0)
11            zero_count += num ^ 1
12          
13            # If we have more than 1 zero in current window
14            if zero_count > 1:
15                # Remove the leftmost element from window
16                # If it was a zero, decrement zero counter
17                zero_count -= nums[left] ^ 1
18                # Move left pointer forward
19                left += 1
20      
21        # The maximum window size is from left pointer to end of array
22        # This represents the longest subarray with at most one zero
23        return len(nums) - left
24
1class Solution {
2    public int findMaxConsecutiveOnes(int[] nums) {
3        // Left pointer for sliding window
4        int left = 0;
5      
6        // Counter for number of zeros in current window
7        int zeroCount = 0;
8      
9        // Iterate through array with right pointer (implicit)
10        for (int num : nums) {
11            // If current number is 0, increment zero count
12            // XOR with 1 converts: 0 -> 1 (true), 1 -> 0 (false)
13            zeroCount += num ^ 1;
14          
15            // If we have more than 1 zero in window, shrink from left
16            if (zeroCount > 1) {
17                // Remove leftmost element from window
18                // If it was a zero, decrement zero count
19                zeroCount -= nums[left] ^ 1;
20                left++;
21            }
22        }
23      
24        // Maximum window size is from left pointer to end of array
25        return nums.length - left;
26    }
27}
28
1class Solution {
2public:
3    int findMaxConsecutiveOnes(vector<int>& nums) {
4        // Left pointer for sliding window
5        int left = 0;
6      
7        // Count of zeros in current window
8        int zeroCount = 0;
9      
10        // Iterate through the array
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 than 1 zero in the window, shrink from left
17            if (zeroCount > 1) {
18                // Remove leftmost element from window
19                // If it was a zero, decrement zero count
20                zeroCount -= nums[left] ^ 1;
21                left++;
22            }
23        }
24      
25        // Maximum window size is from left pointer to end of array
26        return nums.size() - left;
27    }
28};
29
1function findMaxConsecutiveOnes(nums: number[]): number {
2    // Left pointer of the sliding window
3    let leftPointer: number = 0;
4    // Count of zeros in the current window
5    let zeroCount: number = 0;
6  
7    // Iterate through each element in the array
8    for (const currentNum of nums) {
9        // If current number is 0, increment zero count (XOR with 1 flips bits: 0^1=1, 1^1=0)
10        zeroCount += currentNum ^ 1;
11      
12        // If we have more than 1 zero in the window, shrink from left
13        if (zeroCount > 1) {
14            // Remove the leftmost element from zero count and move left pointer
15            zeroCount -= nums[leftPointer] ^ 1;
16            leftPointer++;
17        }
18    }
19  
20    // The maximum window size is from leftPointer to end of array
21    return nums.length - leftPointer;
22}
23

Time and Space Complexity

Time Complexity: O(n), where n is the length of the array nums. The algorithm iterates through the array exactly once with a single for loop. Although there's a while-loop-like behavior with the sliding window (when cnt > 1), each element is processed at most twice - once when the right pointer encounters it and at most once when the left pointer moves past it. This maintains a linear time complexity.

Space Complexity: O(1). The algorithm only uses a constant amount of extra space for the variables l (left pointer) and cnt (counter), regardless of the input size. No additional data structures that scale with the input are created.

Common Pitfalls

Pitfall 1: Misunderstanding the Window Sliding Logic

Many people initially think the algorithm shrinks the window when encountering a violation (more than one 0), but it actually maintains the window size and slides it forward. This can lead to confusion about why we return len(nums) - left instead of tracking the maximum window size explicitly.

Why this happens: The code appears to only move left by 1 when we have too many zeros, which seems like it's not properly shrinking the window to become valid again.

Solution: Understand that this is an optimization. Once we achieve a window of size k, we're only interested in finding larger windows. By sliding (not shrinking) the window, we maintain the best size found so far. The final position of left tells us how much we've shifted from the start, giving us the maximum window size.

Pitfall 2: Incorrect Handling of All-1s Arrays

When the array contains only 1s (no zeros to flip), some might think special handling is needed or that the algorithm won't work correctly.

Example: For [1, 1, 1, 1], some might worry the algorithm won't account for the case where no flip is needed.

Solution: The algorithm handles this naturally. When there are no zeros, zero_count never exceeds 1, so left stays at 0, and we correctly return len(nums) - 0 = len(nums).

Pitfall 3: Off-by-One Error in Manual Implementation

When implementing this from scratch, a common mistake is updating the zero count before moving the left pointer:

# Incorrect order
if zero_count > 1:
    left += 1
    zero_count -= nums[left] ^ 1  # Bug: using new left position

Solution: Always remove the element at the current left position from the count before incrementing left:

# Correct order
if zero_count > 1:
    zero_count -= nums[left] ^ 1  # Use current left position
    left += 1

Pitfall 4: Overcomplicating with Maximum Tracking

Some solutions unnecessarily track the maximum window size explicitly:

# Overcomplicated
max_length = 0
for right in range(len(nums)):
    # ... window logic ...
    max_length = max(max_length, right - left + 1)
return max_length

Solution: The optimized approach eliminates this by recognizing that the window only grows or maintains its size, never truly shrinks, so the final window size is the maximum.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What does the following code do?

1def f(arr1, arr2):
2  i, j = 0, 0
3  new_arr = []
4  while i < len(arr1) and j < len(arr2):
5      if arr1[i] < arr2[j]:
6          new_arr.append(arr1[i])
7          i += 1
8      else:
9          new_arr.append(arr2[j])
10          j += 1
11  new_arr.extend(arr1[i:])
12  new_arr.extend(arr2[j:])
13  return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2  int i = 0, j = 0;
3  List<Integer> newArr = new ArrayList<>();
4
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.add(arr1[i]);
8          i++;
9      } else {
10          newArr.add(arr2[j]);
11          j++;
12      }
13  }
14
15  while (i < arr1.length) {
16      newArr.add(arr1[i]);
17      i++;
18  }
19
20  while (j < arr2.length) {
21      newArr.add(arr2[j]);
22      j++;
23  }
24
25  return newArr;
26}
27
1function f(arr1, arr2) {
2  let i = 0, j = 0;
3  let newArr = [];
4  
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.push(arr1[i]);
8          i++;
9      } else {
10          newArr.push(arr2[j]);
11          j++;
12      }
13  }
14  
15  while (i < arr1.length) {
16      newArr.push(arr1[i]);
17      i++;
18  }
19  
20  while (j < arr2.length) {
21      newArr.push(arr2[j]);
22      j++;
23  }
24  
25  return newArr;
26}
27

Recommended Readings

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

Load More