Brute force all subarrays → Variable-size Sliding Window. Master the "shrink when invalid" pattern.
Problem Statement
A binary array nums (containing only 0s and 1s) and an integer k. Return the maximum number of consecutive 1s in the array if you can flip at most k zeros to ones.
Example 1
Input: nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2
Output: 6
Explanation: Flip the 0s at index 3 and 10 → [1,1,1,1,0,0,1,1,1,1,1]. Wait — the optimal is flipping indices 3 and 5 isn’t contiguous. The best is flipping indices 9 and 10: [1,1,1,0,0,0,1,1,1,1,1] → 5, or indices 3 and 4: [1,1,1,1,1,0,1,1,1,1,0] → 5. Actually the answer of 6 comes from flipping indices 9 and 10: subarray [1,1,1,1,1,1] at positions 5–10 after optimal flips. The window [idx 4..9]: 0,0,1,1,1,1 with 2 flips → length 6. ✅
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: Flip 3 zeros at positions 4, 5, 9 → window of length 10.
The Key Mental Shift 🧠
The problem says "flip at most k zeros to ones." But instead of thinking about flipping, reframe it as:
"Find the longest subarray that contains at most k zeros"
Every 0 inside the window is a flip we’re using. As long as zero-count ≤ k, the window is valid and all those zeros could be flipped.
This makes it a classic variable-size sliding window: expand the right end, and when the window becomes invalid (zeros > k), shrink from the left until it’s valid again.
nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2 — best window (indices 5–10, length 6):
Window [5..10] = [0,1,1,1,1,0] with 2 flips → length 6 ✅
Approaches at a Glance
🐌 Brute Force (All Subarrays)
Try every possible start and end pair. Count zeros in each window. Track the longest valid one.
⚡ Variable Sliding Window (Optimal)
Expand right freely. When zeros exceed k, shrink left until valid. One pass, O(n).
Fixed Window vs Variable Window — What’s Different?
Fixed-size (e.g. LC #643)
Window size is always exactly k. After reaching size k, always shrink by 1 from the left on every step.
Variable-size (this problem)
Window can grow or shrink. Shrink only when the window becomes invalid (zeros > k). Window size fluctuates.
Approach 1 — Brute Force (All Subarrays)
Try every possible subarray by fixing a start index i and expanding the end index j. Count zeros as we expand. If zeros ≤ k, this window is valid — update the max length. If zeros exceed k, break (further expanding will only add more zeros).
- Outer loop:
ifrom0ton−1— each starting position. - Reset
zeros = 0for each new start. - Inner loop:
jfromiton−1— expand end of window. - If
nums[j] == 0, increment zero count. If zeros > k, break early. - Otherwise update
maxLen = max(maxLen, j − i + 1).
🔍 Dry Run — Brute Force
Input: nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2
Showing only key rows (n=11, many iterations omitted for brevity):
| i (start) | j (end) | nums[j] | zeros | zeros ≤ k? | window len | maxLen |
|---|---|---|---|---|---|---|
| 0 | 0 | 1 | 0 | ✅ yes | 1 | 1 |
| 0 | 1 | 1 | 0 | ✅ yes | 2 | 2 |
| 0 | 2 | 1 | 0 | ✅ yes | 3 | 3 |
| 0 | 3 | 0 | 1 | ✅ yes | 4 | 4 |
| 0 | 4 | 0 | 2 | ✅ yes | 5 | 5 |
| 0 | 5 | 0 | 3 | ❌ no | — | 5 (break) |
| … i=1,2,3,4 all yield maxLen ≤ 5 … | ||||||
| 5 | 5 | 0 | 1 | ✅ yes | 1 | 5 |
| 5 | 6 | 1 | 1 | ✅ yes | 2 | 5 |
| 5 | 7 | 1 | 1 | ✅ yes | 3 | 5 |
| 5 | 8 | 1 | 1 | ✅ yes | 4 | 5 |
| 5 | 9 | 1 | 1 | ✅ yes | 5 | 5 |
| 5 | 10 | 0 | 2 | ✅ yes | 6 | 6 ↑ BEST! |
| … remaining start positions yield maxLen ≤ 6 … | ||||||
Approach 2 — Variable Sliding Window (Optimal) ⚡
Intuition — Expand Greedily, Shrink When Forced
Instead of restarting from scratch for each i, maintain a window [windowStart, windowEnd] and track how many zeros are inside it.
- Always expand: Move
windowEndright by 1. Ifnums[windowEnd] == 0, incrementzeros. - Shrink if invalid: While
zeros > k, movewindowStartright. If the element leaving is 0, decrementzeros. - Record: After shrinking, the window
[windowStart, windowEnd]is valid. UpdatemaxLength.
Why the while loop (not if)?
When a single new zero tips zeros over k, we might need to move windowStart past multiple ones before we reach and expel the oldest zero. A while loop keeps shrinking until the window is valid again.
🔍 Dry Run — Sliding Window (Detailed)
Input: nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2
S = windowStart ↑ E = windowEnd ↑
nums[0]=1 → not a zero, just move: windowStart=1
nums[1]=1 → not a zero, just move: windowStart=2
nums[2]=1 → not a zero, just move: windowStart=3
nums[3]=0 → IS a zero! zeros=2, windowStart=4 — now zeros ≤ k, stop shrinking
window=[4..5] • maxLength still = 5
maxLength = max(6, 7) = 7? No — wait, window [4..10] has 2 zeros (idx 5 and 10) and one at idx 4 = 3 zeros!
zeros was 1 before E=10, now zeros=2 ≤ 2 • No shrink.
window=[4..10] • maxLength = max(6, 7) = 7... but the expected answer is 6.
⚠️ Rechecking: at E=9 windowStart=4, zeros=1 (only idx 5). At E=10, zeros becomes 2. window=[4..10], size=7 contains 2 zeros (idx 5,10). Valid! maxLength=7? No, expected=6. Let’s retrace from E=5 shrink carefully...
After E=5 shrink: windowStart=4, zeros=2 (idx 4 and 5 are zeros!). E=6..9 adds 1s, zeros stays 2. E=10 adds 0 → zeros=3 > k → SHRINK: nums[4]=0 → zeros=2, windowStart=5. window=[5..10] size=6. maxLength = max(6,6) = 6 ✅
Each element was added once (windowEnd sweep) and removed at most once (windowStart shrink). Total operations = 2n at most → O(n).
Time & Space Complexity
| Approach | Time | Space | Note |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Nested loops; inner loop re-counts zeros |
| Sliding Window | O(n) | O(1) | Each element enters and leaves the window at most once |
windowStart only ever moves forward — it can advance at most n times across the entire run, so the inner while executes at most n times in total, not n times per iteration.