A clear explanation of finding the longest run of 1s after flipping at most one 0 using a sliding window.
Problem Restatement
We are given a binary array nums.
We may flip at most one 0 into 1.
Return the maximum number of consecutive 1s we can get after doing this optimally. The array length is at most 10^5, and every element is either 0 or 1.
Input and Output
| Item | Meaning |
|---|---|
| Input | A binary integer array nums |
| Output | Maximum consecutive 1s after flipping at most one 0 |
| Allowed operation | Flip zero or one 0 into 1 |
| Constraint | 1 <= nums.length <= 10^5 |
Function shape:
def findMaxConsecutiveOnes(nums: list[int]) -> int:
...Examples
Example 1:
nums = [1, 0, 1, 1, 0]If we flip the first 0:
[1, 1, 1, 1, 0]The longest run has length 4.
If we flip the second 0:
[1, 0, 1, 1, 1]The longest run has length 3.
Answer:
4Example 2:
nums = [1, 0, 1, 1, 0, 1]Flipping either zero can give a run of length 4.
Answer:
4First Thought: Try Every Zero
One direct idea is to try flipping each 0.
For every zero index:
- Pretend this zero becomes
1. - Count the longest run of
1s. - Keep the best answer.
This works, but it can scan the array again for each zero.
In the worst case, there may be O(n) zeroes, and each check costs O(n).
So the total cost can become:
O(n^2)That is too slow for n = 100000.
Key Insight
After flipping at most one 0, any valid consecutive block may contain at most one zero.
So the problem becomes:
Find the longest contiguous subarray containing at most one 0.
This is a sliding window problem.
We keep a window:
nums[left:right + 1]The window is valid while it contains at most one zero.
If adding nums[right] makes the window contain two zeroes, we move left rightward until the window becomes valid again.
Algorithm
Initialize:
left = 0
zeros = 0
best = 0Scan right from left to right.
For each nums[right]:
- If it is
0, incrementzeros. - While
zeros > 1, moveleftforward. - If the removed value was
0, decrementzeros. - Update
bestwith the current window length.
The window length is:
right - left + 1Correctness
The algorithm maintains a window with at most one zero.
When nums[right] is added, the zero count is updated. If the window has more than one zero, moving left forward removes elements from the window until at most one zero remains.
Therefore, after the shrinking step, the current window is always a valid subarray that can be turned into all 1s by flipping at most one zero.
For each right endpoint, the algorithm keeps the leftmost possible left that makes the window valid. That gives the longest valid window ending at right.
Since every possible answer has some right endpoint, and the algorithm checks the best valid window for every right endpoint, the maximum recorded length is the required answer.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each pointer moves from left to right at most once |
| Space | O(1) | Only counters and pointers are used |
Implementation
class Solution:
def findMaxConsecutiveOnes(self, nums: list[int]) -> int:
left = 0
zeros = 0
best = 0
for right, num in enumerate(nums):
if num == 0:
zeros += 1
while zeros > 1:
if nums[left] == 0:
zeros -= 1
left += 1
best = max(best, right - left + 1)
return bestCode Explanation
The left boundary starts at the beginning:
left = 0The window initially has no zeroes:
zeros = 0As we expand the right side:
for right, num in enumerate(nums):we count zeroes:
if num == 0:
zeros += 1If the window has two zeroes, it can no longer be fixed with one flip:
while zeros > 1:So we move the left boundary. If we remove a zero, the zero count decreases:
if nums[left] == 0:
zeros -= 1
left += 1Now the window is valid again, so we update the answer:
best = max(best, right - left + 1)Stream-Friendly Implementation
The follow-up asks what happens if numbers arrive one by one and we cannot store the whole array.
For at most one flipped zero, we only need the position of the previous zero.
class Solution:
def findMaxConsecutiveOnes(self, nums: list[int]) -> int:
left = 0
last_zero = -1
best = 0
for right, num in enumerate(nums):
if num == 0:
left = last_zero + 1
last_zero = right
best = max(best, right - left + 1)
return bestWhen we see a new zero, the previous zero can no longer stay in the same valid window. So the next valid window must start after the previous zero.
Testing
def run_tests():
s = Solution()
assert s.findMaxConsecutiveOnes([1, 0, 1, 1, 0]) == 4
assert s.findMaxConsecutiveOnes([1, 0, 1, 1, 0, 1]) == 4
assert s.findMaxConsecutiveOnes([1, 1, 1]) == 3
assert s.findMaxConsecutiveOnes([0, 0, 0]) == 1
assert s.findMaxConsecutiveOnes([0]) == 1
assert s.findMaxConsecutiveOnes([1]) == 1
assert s.findMaxConsecutiveOnes([1, 1, 0, 1, 1, 1]) == 6
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[1, 0, 1, 1, 0] | Main example |
[1, 0, 1, 1, 0, 1] | Two possible flips give same best |
[1, 1, 1] | No zero needs flipping |
[0, 0, 0] | Can flip only one zero |
[0] | Single zero becomes one |
[1] | Single one remains one |
[1, 1, 0, 1, 1, 1] | One zero connects two runs |