DSA sheet · Arrays · Sliding Window pattern (variable size, longest)

Max Consecutive Ones III

The "part three" of Max Consecutive Ones: now you may flip up to k zeros into ones. The teacher solves it three times: a brute force (O(n²)), a better sliding window that shrinks with a while loop (she calls it O(2n)), and the most optimised version where the left pointer moves with an if, at most one step per step of right, so the window never gets smaller, and the answer is simply n − left.

Why it matters: this is the standard "longest window with at most k bad cells" problem (Pattern 4 in the overview). The same code, with "bad cell" redefined, solves Longest Repeating Character Replacement, Fruits Into Baskets and more. And the never-shrinking window trick is a favourite follow-up question in interviews.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

Window, expand, shrink

A subarray = a contiguous piece of the array = a window [left..right], both ends included, length right − left + 1. The brute force tries every start and re-walks overlapping windows: O(n²). A sliding window moves right forward once over the array (expand: a cell joins) and moves left forward only when needed (shrink: the oldest cell leaves). Neither moves back.

Fixed vs variable

FIXED size (problem 2)VARIABLE size (this problem)
sizegiven kunknown; the longest valid window is the answer
left movesone step with every right steponly when the window becomes invalid (too many zeros)
recordafter every slidelongest: after shrinking back to valid

Careful: in this problem k is not a window size. It's the number of zeros you may flip. The window size is free.

Other shapes in this notebook: shortest valid (record while shrinking), count of valid windows (count += right − left + 1), and "exactly K = atMost(K) − atMost(K − 1)". All of them need a one-way condition, and here we have one: the number of zeros in a window can only go up when it grows and down when it shrinks. So once a window has too many zeros, growing can't fix it; only shrinking can. (Sum problems lose this property when negatives are allowed.)

The "bad cell" counter

We don't need a full frequency map here: the only thing that matters is how many zeros are inside. One integer, zero_count, plays the role of the map. +1 when a 0 joins, −1 when a 0 leaves. The window is valid while zero_count ≤ k, because then we can flip all its zeros.


Part A · Brute force: every start, count zeros

LeetCode 1004

1The question in simple words

You get a binary array nums (every value is 0 or 1) and an integer k. You may change at most k zeros into ones. Return the longest run of consecutive 1s you can end up with.

Another way to say it: find the longest window that contains at most k zeros. Flip those zeros and the whole window is 1s.

index012345678910
nums11100011110k = 2 → answer 6

Her walk through the example:

No window has 7 cells with only two zeros, so the answer is 6. A second LeetCode example: [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], k = 3 → 10.

2What the constraints tell us

3Intuition

Walk forward from a start. 1s are always fine. Every 0 uses up one flip. As long as we've used ≤ k flips, keep going. The moment we meet the (k + 1)-th zero, this start can't go further. Its length is a candidate. Try every start, keep the best.

4Building the logic from the example

  1. Start at index 0. 1 → fine. 1 → fine. 1 → fine. 0 → zero_count = 1. Is 1 > k = 2? No, keep going.
  2. 0 → zero_count = 2. Still not > 2, keep going.
  3. 0 at index 5 → zero_count = 3 > 2. Stop here. This start's best window is 0..4, length 5.
  4. To get a length we need to remember where we started, so she keeps a left pointer at the start and moves a right pointer forward.
Doubt 1: she says the length is right − left, not right − left + 1. Why?
→ At the moment we stop, right is standing on the (k + 1)-th zero, which is not allowed in the window. Normally length = right − left + 1, but we must not count the cell at right, so subtract 1: right − left + 1 − 1 = right − left. Here: 5 − 0 = 5 ✓.
Doubt 2: she offers a second way: update the length at every step with j − i + 1. Which one should I use?
→ The every-step way. If you only measure at the break (j − i), a start whose walk reaches the end of the array without hitting k + 1 zeros is never measured. E.g. all ones, or k ≥ number of zeros: the answer would come out as 0. This is the same trap as in problem 3. With "update at every valid step", we measure while j is still standing on an allowed cell (the break check has just passed), so j belongs to the window and the length is j − i + 1. The code below does this.
Doubt 3: where do I reset zero_count?
→ At the start of every i, inside the outer loop. Each start counts its own zeros. If you set it to 0 once outside both loops, zeros from the previous start leak in.

She sets max_len = 0 at the start: even in the worst case (for example all zeros with k = 0) the answer is 0, never negative.

5Approach steps

  1. max_len = 0.
  2. For each start i: zero_count = 0.
  3. For each j from i: if nums[j] == 0, zero_count += 1.
  4. If zero_count > k: break (this start is done).
  5. Otherwise [i..j] is valid: max_len = max(max_len, j − i + 1).
  6. Return max_len.

6Code (Python)

Brute force: every start, O(n²)
class Solution:
    def longestOnes(self, nums, k):
        n = len(nums)
        max_len = 0
        for i in range(n):
            zero_count = 0                       # fresh for every start
            for j in range(i, n):
                if nums[j] == 0:
                    zero_count += 1
                if zero_count > k:               # (k+1)-th zero: can't flip it
                    break
                max_len = max(max_len, j - i + 1)   # [i..j] is valid
        return max_len

7Code line by line

linewhat it means
for i in range(n):Every index gets a turn as the left end.
zero_count = 0No flips used yet for this start.
if nums[j] == 0: zero_count += 1This 0 will need a flip.
if zero_count > k: breakMore zeros than flips. Every longer window from this i also contains them, so stop.
max_len = max(max_len, j - i + 1)j is inside a valid window, so count it (+1). Done on every valid step, so a walk that reaches the end is also measured.

8Dry run (hand table)

nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2.

ij walksstops becauselongest valid [i..j]max_len
00 → 53rd zero at 5[0..4] = 55
11 → 53rd zero at 5[1..4] = 45
22 → 53rd zero at 5[2..4] = 35
33 → 53rd zero at 5[3..4] = 25
44 → 103rd zero at 10[4..9] = 66
55 → endonly 2 zeros left, reaches the end[5..10] = 66
6..10to the endend5, 4, 3, 2, 16

Answer: 6 ✓. Look at rows 1, 2, 3: they stop at the same zero as row 0 and are just shorter. That's the waste Part B removes.

9Complexity & remember

Remember the brute forceFor each i: reset zero_count, walk j, break on the (k+1)-th zero, record j − i + 1 at every valid step.

Part B · Better: sliding window, shrink with while

1The question again, with the new goal

Same question. Goal: stop re-walking from every start. Each pointer should cross each index only once.

2What the constraints tell us now

n up to 10⁵ → we want O(n). Values are only 0/1 and we want the longest contiguous window, where every cell inside matters → sliding window (like problem 3).

3Intuition: don't restart right; move left past a zero

In the brute force, after i = 0 stopped at the 3rd zero (index 5), we moved i to 1 and started j all over again from 1, only to stop at the same index 5 with a shorter window. Same for i = 2 and i = 3.

Her observation: the answer only changes when a zero leaves the window. Moving left past 1s just makes the window shorter while the zero count stays at 3. So:

The two phases have names: right moving = the expansion phase; left moving because the window broke = the shrinking phase.

4Building the code from the example

  1. left = 0, zero_count = 0, max_len = 0. A for loop moves right from 0 to n − 1.
  2. If nums[right] == 0 → zero_count += 1.
  3. If zero_count > k → shrink. She first writes this as an if with a break, then notices two problems: (a) a break there would end the whole for loop, not just the shrinking; (b) left may need to move several times (past 1, 1, 1 and then the 0). So it must be a while loop: while zero_count > k.
  4. Inside the while: before moving left, look at the cell it's leaving. If nums[left] == 0, a zero is leaving, so zero_count -= 1. Then left += 1.
  5. After the while, the window [left..right] is valid again. Now the length is right − left + 1 (right is a valid cell this time, so +1). Update max_len.
Doubt 1: why check nums[left] before left += 1 and not after?
→ We need to know what is leaving. After left += 1, nums[left] is the next cell, which is still inside. Checking that would decrement for the wrong cell.
Doubt 2: after the 0 at index 3 leaves, the count is 2 = k. Why stop there and not remove more?
→ The rule is "at most k". 2 zeros = exactly 2 flips, which is allowed. Removing more would only shorten a valid window, and we want the longest.
Doubt 3: why is the length right − left + 1 here, but right − left in the brute force?
→ In the brute force we measured at the moment right stood on the bad zero. Here we measure after the shrinking, when the bad zero has been paid for and [left..right] is fully valid, so right counts.

5Approach steps

  1. left = 0, zero_count = 0, max_len = 0.
  2. For right from 0 to n − 1: if nums[right] is 0 → zero_count += 1. (expand)
  3. While zero_count > k: if nums[left] is 0 → zero_count −= 1; left += 1. (shrink)
  4. max_len = max(max_len, right − left + 1).
  5. Return max_len.

6Code (Python)

Better: sliding window with while-shrink, O(2n)
class Solution:
    def longestOnes(self, nums, k):
        left = 0
        zero_count = 0
        max_len = 0
        for right in range(len(nums)):
            if nums[right] == 0:
                zero_count += 1                  # expand: a zero joined
            while zero_count > k:                # too many zeros: shrink
                if nums[left] == 0:
                    zero_count -= 1              # a zero is leaving
                left += 1
            max_len = max(max_len, right - left + 1)   # valid again
        return max_len

7Code line by line

linewhat it means
for right in range(len(nums)):Right visits every index once and never restarts.
if nums[right] == 0: zero_count += 1Count the zeros currently inside the window.
while zero_count > k:The window needs more flips than we have. A while, because several cells may have to leave before a zero does.
if nums[left] == 0: zero_count -= 1Look at the cell that is about to leave. Only a leaving zero lowers the count.
left += 1The cell leaves (it turns grey in the pictures).
max_len = max(max_len, right - left + 1)The window is valid now; record its length.

8Dry run (hand table)

nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2.

stepright (value added)window beforezero_count after addingshrink? (what leaves, why)window aftermax_len
10 (1)[0..0]0no[0..0]1
21 (1)[0..1]0no[0..1]2
32 (1)[0..2]0no[0..2]3
43 (0)[0..3]1no, 1 ≤ 2[0..3]4
54 (0)[0..4]2no, 2 ≤ 2[0..4]5
65 (0)[0..5]3yes, 3 > 2: 1, 1, 1 leave (count stays 3), then the 0 at index 3 leaves → 2[4..5]5
76 (1)[4..6]2no[4..6]5
87 (1)[4..7]2no[4..7]5
98 (1)[4..8]2no[4..8]5
109 (1)[4..9]2no[4..9]6
1110 (0)[4..10]3yes: the 0 at index 4 leaves → 2[5..10]6
step 6 in111000111103 zeros > k → shrink
L    R     
step 6 out11100011110left walked 4 steps; right waited
    LR     
step 1011100011110length 6 = answer
    L    R 

9Complexity & remember

She calls this "better, but not yet the most optimised", because in step 6 the right pointer had to wait while left walked 4 steps.

Remember the better solutionExpand: count zeros. While zeros > k: if the leaving cell is 0, decrement; left += 1. Record right − left + 1. Each pointer moves at most n times.

Part C · Most optimised: shift with if, return n − left

1The question again, with the new goal

Same question. Goal: exactly one pass, without right ever waiting for left. (Her interview tip below explains why this version is worth knowing.)

2What changes

Everything is the same as Part B (left, zero_count, the for loop over right, counting zeros) except two things:

  1. The while becomes an if. When there are too many zeros, left moves only one step, and right carries on. Left catches up a little on each following step instead of all at once.
  2. There is no max_len variable. At the end she returns n − left.

3Intuition: the window never gets smaller

In Part B the window shrank back to valid at once. Here, when it's invalid, left and right both step forward by one, so the window keeps its size and just slides. It only grows on a step where the if doesn't fire (the window is valid).

Think of it like this: the window's size is a "record" of the longest valid window found so far. We never need a smaller window, because a smaller one can't beat the record. So instead of shrinking, we slide the record-sized window forward and wait until it can grow again.

Her words for it: we aren't measuring lengths along the way, we're finding the best position for left by the time right reaches the last index.

4Building it from the example (her walk-through)

  1. Right moves over 1, 1, 1: nothing happens. Then 0 → count 1, 0 → count 2 (not > 2). Window [0..4], size 5.
  2. Right on index 5 (0) → count 3 > 2. In Part B, right would now wait. Here: check nums[left] (index 0, a 1, so no change to the count), left += 1 once. Window [1..5], still size 5.
  3. Right moves to 6 (a 1). The count is still 3 > 2 (three zeros at 3, 4, 5 are still inside), so left moves one more step → [2..6].
  4. Right to 7. Still 3 → left steps → [3..7].
  5. Right to 8. Still 3 → this time nums[left] (index 3) is a 0, so the count drops to 2, then left += 1 → [4..8].
  6. Right to 9 (a 1). Count 2, not > 2 → left stays. The window grows to [4..9], size 6.
  7. Right to 10 (a 0) → count 3 > 2 → nums[left] (index 4) is 0 → count 2, left → 5. Window [5..10], size 6.
  8. The loop ends. The window size is the answer: 6.

Why n − left?

When the for loop finishes, right has gone past the last index. In her Java loop it ends equal to n, which is out of bounds, so the window length is right − left (no +1, because index n isn't a real cell) = n − left. In Python's for loop right stops at n − 1, and (n − 1) − left + 1 is also n − left. Same answer: 11 − 5 = 6 ✓.

Doubt 1: in step 2 the window [1..5] has three zeros. It isn't valid! Isn't that a bug?
→ It's fine because we never record it as a new answer. Its size (5) is the size of a window we already saw being valid ([0..4]). The window is just sliding at the record size. The size only goes up on a step where the if doesn't fire, and at that moment zero_count ≤ k, so the bigger window really is valid. So the final size is always the size of some real valid window.
Doubt 2: could we miss a longer valid window because left was moved too early?
→ No. Suppose a valid window of length L + 1 ends at the current right, and our window has size L before adding right. After adding right, our window is the last L + 1 cells, which is exactly that valid window (or sits inside a longer valid one). So it has ≤ k zeros, the if doesn't fire, and the window grows to L + 1. The window grows exactly when a longer valid window exists. (This proof is my addition. The tests check it against brute force on hundreds of random arrays.)
Doubt 3: zero_count can be more than k + 1 here?
→ No. Each step adds at most one zero and, when the count is above k, removes at most one. Once it's k + 1 it can stay k + 1 for a while (steps 2–4), but never climbs higher.

5Approach steps

  1. left = 0, zero_count = 0, n = len(nums).
  2. For right from 0 to n − 1: if nums[right] is 0 → zero_count += 1.
  3. If zero_count > k: if nums[left] is 0 → zero_count −= 1; then left += 1 (just once).
  4. After the loop → return n − left.

6Code (Python)

Most optimised: if-shift, never-shrinking window, O(n)
class Solution:
    def longestOnes(self, nums, k):
        n = len(nums)
        left = 0
        zero_count = 0
        for right in range(n):
            if nums[right] == 0:
                zero_count += 1
            if zero_count > k:                   # IF, not while: slide by one
                if nums[left] == 0:
                    zero_count -= 1
                left += 1
        return n - left                          # final window size = best length

7Code line by line

linewhat it means
if nums[right] == 0: zero_count += 1Same as before: count zeros inside [left..right].
if zero_count > k:The window is too "bad". Don't shrink it, just stop it from growing.
if nums[left] == 0: zero_count -= 1Check the leaving cell before moving, as in Part B.
left += 1One step. Together with right's step, the size stays the same.
return n - leftRight ended at the last index, so the window is [left..n−1], length n − left, which equals the longest valid length.

8Dry run (hand table)

nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2.

stepright (value added)window beforezero_count after addingshift? (what leaves, why)window aftersize (= answer so far)
1–30..2 (1s)[0..2]0no[0..2]3
43 (0)[0..3]1no[0..3]4
54 (0)[0..4]2no[0..4]5
65 (0)[0..5]3yes, 3 > 2: index 0 (a 1) leaves, count stays 3[1..5]5
76 (1)[1..6]3yes: index 1 (a 1) leaves, count 3[2..6]5
87 (1)[2..7]3yes: index 2 (a 1) leaves, count 3[3..7]5
98 (1)[3..8]3yes: index 3 (a 0) leaves → count 2[4..8]5
109 (1)[4..9]2no, 2 ≤ 2 → window grows[4..9]6
1110 (0)[4..10]3yes: index 4 (a 0) leaves → count 2[5..10]6
endreturn n − left = 11 − 56
step 611100011110only one step for left; size stays 5
 L   R     
step 911100011110a zero finally left: count back to 2
    L   R  
end11100011110size 6 → n − left = 6
     L    R

Compare with Part B at the same moment (step 6): there left jumped to 4 at once while right waited. Here left reaches 4 at step 9, moving together with right.

9Complexity & remember

Her submission beat about 99.86%. She also notes that Part B was already fast (O(2n) is linear too). In Big-O both are O(n). The real savings are the waiting steps and the max() call on every step.

Her interview tipIn the interview, give the brute force idea, then the better solution (Part B). If the interviewer says "you used O(2n), can you optimise further?", they're asking for this exact O(n) version: if instead of while, and return n − left.
Remember the optimalSame as Part B, but if instead of while → the window never shrinks, only slides or grows. No max variable: return n − left.

Part D · Revision page

Brute forceBetter (while)Optimal (if)
ideaevery start, walk j, break on (k+1)-th zeroshrink left until zeros ≤ kslide left by one when zeros > k
window size—shrinks and growsnever shrinks
answer frommax of j − i + 1max of right − left + 1n − left
right waits?restarts every iyes, during the whilenever
time / spaceO(n²) / O(1)O(2n) / O(1)O(n) / O(1)
Max Consecutive Ones (problem 3)Max Consecutive Ones III
zeros allowed in the window0up to k
on a bad zeroleft jumps past itleft moves until one zero leaves
same code?yes: problem 3 = this problem with k = 0
If you remember only 5 lines 1. Rephrase: longest window with at most k zeros.
2. zero_count is the only state: +1 when a 0 joins, −1 when a 0 leaves.
3. Check nums[left] before left += 1.
4. while-shrink → record right − left + 1 (O(2n)).
5. if-shift → window never shrinks → return n − left (O(n)).
Mistakes to avoid ✗ treating k as a window size (it's a number of flips)
✗ resetting zero_count outside the outer loop (brute force)
✗ measuring only at the break (misses runs that reach the end)
✗ break inside the for loop instead of a while for shrinking
✗ decrementing zero_count after left += 1 (wrong cell)
✗ returning n − left from the while version (there the final window can be shorter than the best one)
test it yourself (paste under any Solution above)
s = Solution()
print(s.longestOnes([1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0], 2))                          # 6
print(s.longestOnes([0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1], 3))  # 10
print(s.longestOnes([1, 1, 0, 1], 0))      # 2  (k = 0 is problem 3)
print(s.longestOnes([0, 0, 0], 5))         # 3  (k >= n: whole array)
print(s.longestOnes([0, 0, 0], 0))         # 0  (no valid window)

Based on this video: Max Consecutive Ones III | Brute → Better → Optimal