Anonymous

On Day 10, we used a fixed sliding window to find the maximum average subarray. The window had a fixed size k, and the key operation was carrying the previous sum forward and adjusting it by adding one element and removing one element. The state we tracked was numeric: a running sum.
Today the state we track is simpler, but the discipline required to manage it correctly is the same. We are working with a binary array of only zeros and ones, and we want to know the longest unbroken run of ones. There is no sum to carry forward. There is no window to slide. There is just a streak counter that grows while we see ones and resets when we see a zero.
The code is short. But there is one edge case that catches most people the first time: what happens when the array ends mid-streak? A streak that reaches the final element of the array is never terminated by a zero, which means if you only finalize the streak when you see a zero, you will miss it entirely. That single detail is what separates a correct solution from a subtly wrong one, and it is exactly what interviewers test.
Day 11 is LeetCode 485: Max Consecutive Ones. It is tagged Easy, and it is a clean exercise in state management and edge case awareness. Getting it right the first time, and being able to explain why it is right, is the goal.
Given a binary arraynums, return the maximum number of consecutive1s in the array.
Example 1:
Input: nums = [1, 1, 0, 1, 1, 1]
Output: 3Example 2:
Input: nums = [1, 0, 1, 1, 0, 1]
Output: 2Constraints:
1 <= nums.length <= 10^5nums[i] is either 0 or 1Here is the observation that drives this entire problem: a streak of consecutive ones is a contiguous run that ends in exactly one of two ways. It either hits a zero and is terminated mid-array, or it reaches the last element of the array and is terminated by the boundary.
Most intuitive implementations only handle the first case. They increment a counter when they see a one and finalize the maximum when they see a zero. This works perfectly when every streak is followed by a zero. But if the array ends with a streak of ones, that streak is never followed by a zero. The update to maxCons never happens for it, and the answer is wrong.
The fix is to finalize the streak at two moments: when a zero is encountered, and when the last index is reached. Both are termination events for the active streak. A correct solution must handle both.
The brute force approach generates every possible subarray, counts the ones in each, and tracks the maximum. That is O(n^2) and completely unnecessary here. We can answer the question in a single pass by just keeping track of the length of the current streak.
The most common first attempt looks like this:
// Incomplete: misses the final streak if array ends with 1s
var findMaxConsecutiveOnes = function(nums) {
let maxCons = 0, currCons = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] === 1) {
currCons++;
} else {
maxCons = Math.max(maxCons, currCons);
currCons = 0;
}
}
return maxCons; // wrong if array ends with 1s
};For the input [1, 1, 0, 1, 1, 1], this returns 2 instead of 3. The first streak is correctly finalized when the zero at index 2 is hit. But the second streak, which runs from index 3 to index 5, is never finalized because there is no zero after it. The loop ends with currCons = 3 sitting unused.
The mental shift is to recognize that the else branch is not the only finalization point. It handles the interior termination case. The final index handles the boundary termination case. Both need the same update logic.
One clean way to handle this is to finalize maxCons after the loop ends:
// Also correct: finalize after loop
maxCons = Math.max(maxCons, currCons);
return maxCons;The approach in the original solution handles it inline by checking i == nums.length - 1 as a second trigger condition. Both approaches are valid. The post-loop update is arguably cleaner because it removes the in-loop special case entirely.
We use two variables throughout the algorithm.
currCons holds the length of the streak of ones we are currently building. It increments by one every time we see a 1 and resets to 0 every time we finalize a streak. maxCons holds the length of the longest streak we have finalized so far. It starts at 0 and only updates when a streak ends.
The rules are as follows. When nums[i] == 1, we increment currCons. When nums[i] == 0 or when i is the last index, we compare currCons with maxCons, update maxCons if the current streak is longer, and reset currCons to 0.
The two conditions for finalization, a zero encountered or the last index reached, are handled together in the same if block using a logical OR. They represent the same event from the streak's perspective: the streak is over.
We will trace through nums = [1, 1, 0, 1, 1, 1] completely.
Initial state:
nums = [1, 1, 0, 1, 1, 1]
indices = 0 1 2 3 4 5
currCons = 0
maxCons = 0Iteration 1 (i = 0):
[1] 1 0 1 1 1
^
i
nums[i] = 1
nums[i] == 1 → increment currCons.
Not zero, not last index → no finalization.
currCons = 1
maxCons = 0First one seen. Streak begins. No finalization yet.
Iteration 2 (i = 1):
1 [1] 0 1 1 1
^
i
nums[i] = 1
nums[i] == 1 → increment currCons.
Not zero, not last index → no finalization.
currCons = 2
maxCons = 0Second consecutive one. Streak grows to 2.
Iteration 3 (i = 2):
1 1 [0] 1 1 1
^
i
nums[i] = 0
nums[i] == 0 → finalize streak.
maxCons = max(0, 2) = 2
currCons = 0Zero encountered. The first streak of length 2 is finalized. maxCons updates to 2. Counter resets.
Iteration 4 (i = 3):
1 1 0 [1] 1 1
^
i
nums[i] = 1
nums[i] == 1 → increment currCons.
Not zero, not last index → no finalization.
currCons = 1
maxCons = 2New streak begins after the zero. Length is 1 so far.
Iteration 5 (i = 4):
1 1 0 1 [1] 1
^
i
nums[i] = 1
nums[i] == 1 → increment currCons.
Not zero, not last index → no finalization.
currCons = 2
maxCons = 2Streak grows to 2. Still not at the last index.
Iteration 6 (i = 5):
1 1 0 1 1 [1]
^
i = last index (nums.length - 1 = 5)
nums[i] = 1
nums[i] == 1 → increment currCons.
i == nums.length - 1 → finalize streak (last index reached).
currCons = 3
maxCons = max(2, 3) = 3
currCons = 0The final element is a one. currCons increments to 3, and because this is the last index, the streak is immediately finalized. maxCons updates to 3.
Loop ends.
return maxCons = 3
Final output: 3. Correct. The trailing streak [1, 1, 1] from index 3 to 5 was correctly captured.
Bonus trace for Example 2: nums = [1, 0, 1, 1, 0, 1]
i=0: 1, currCons=1. i=1: 0, finalize: maxCons=1, reset. i=2: 1, currCons=1. i=3: 1, currCons=2. i=4: 0, finalize: maxCons=max(1,2)=2, reset. i=5: 1 and last index, currCons=1, finalize: maxCons=max(2,1)=2.
Output: 2. Correct.
1. A streak has exactly two types of termination events. Interior termination happens when a zero is encountered mid-array. Boundary termination happens when the last index is reached while still inside a streak. Both must trigger the same finalization logic. Missing either one produces an incorrect answer on at least some inputs.
2. Resetting currCons after finalization is correct even at the last index. After finalizing the streak at the last index, we reset currCons to 0. This reset is harmless since the loop exits immediately afterward. Some candidates worry about whether the reset corrupts something, but there is nothing to corrupt. maxCons has already been updated.
3. Incrementing before checking the last-index condition is the correct order. At the last index, if nums[i] == 1, we increment currCons first and then finalize. This means the final 1 is counted as part of the streak before we compare. If we finalized before incrementing, the last element would be excluded from its own streak, which is wrong.
4. The invariant holds throughout: maxCons always contains the length of the longest fully processed streak. At any point during iteration, maxCons reflects the best streak seen among all streaks that have ended so far. currCons reflects the length of the streak currently in progress, which has not been finalized yet. The combination of both variables always captures the complete state.
var findMaxConsecutiveOnes = function(nums) {
let maxCons = 0;
let currCons = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] == 1) {
currCons++;
}
if (nums[i] == 0 || i == nums.length - 1) {
maxCons = currCons > maxCons ? currCons : maxCons;
currCons = 0;
}
}
return maxCons;
};We initialize both variables to 0. maxCons represents no streak seen yet. currCons represents no active streak.
The first if block handles streak growth. When the current element is 1, we increment currCons. This block does not trigger any finalization.
The second if block handles streak finalization. It fires under two independent conditions: either the current element is 0 (interior termination) or the current index is the last index in the array (boundary termination). When it fires, we update maxCons using a ternary comparison and reset currCons to 0.
Note that both if blocks can fire on the same iteration. If nums[i] == 1 and i == nums.length - 1, the first block increments currCons and the second block immediately finalizes it. The ordering is intentional: increment first, finalize second, so the last element is included in its streak.
If nums[i] == 0, only the second block fires. currCons is not incremented for a zero before it is reset, which is correct because zeros do not belong to any streak.
We return maxCons after the loop completes.
| Complexity | Explanation | |
|---|---|---|
| Time | O(n) | A single pass through the array. Every element is visited exactly once. |
| Space | O(1) | Only two integer variables are maintained regardless of the size of the input. |
Mistake 1: Forgetting to finalize the streak at the end of the array.
This is the most common error on this problem, and it is subtle because both provided examples happen to end differently. Example 1 ends with a one-streak (exposes the bug) but Example 2 also ends with a one-streak of length 1. Any implementation that only finalizes on zeros will fail on inputs ending with a run of ones.
// Wrong: returns 2 for [1, 1, 0, 1, 1, 1] instead of 3
for (let i = 0; i < nums.length; i++) {
if (nums[i] === 1) currCons++;
else { maxCons = Math.max(maxCons, currCons); currCons = 0; }
}
return maxCons; // currCons = 3 is never capturedThe fix is to add maxCons = Math.max(maxCons, currCons) after the loop, or to check i == nums.length - 1 inside the loop.
Mistake 2: Incrementing currCons for zeros.
Some candidates use a single if/else but accidentally increment in the wrong branch:
// Wrong: increments on every element, not just ones
if (nums[i] == 0) {
maxCons = Math.max(maxCons, currCons);
currCons = 0;
} else {
currCons++; // this is correct
}The logic here is actually right. But the danger is writing currCons++ outside the conditional entirely and applying it regardless of whether the element is 0 or 1. Always confirm the increment is gated on nums[i] == 1.
Mistake 3: Using maxCons = currCons instead of taking the max.
If you overwrite maxCons with currCons unconditionally on every finalization, later shorter streaks will overwrite earlier longer ones.
// Wrong: shorter streaks overwrite the best result
maxCons = currCons; // should be maxCons = Math.max(maxCons, currCons)Always compare before updating.
Mistake 4: Resetting currCons before updating maxCons.
Swapping the two lines in the finalization block means you compare the reset value (0) against maxCons instead of the actual streak length.
// Wrong: resets before comparing
currCons = 0;
maxCons = Math.max(maxCons, currCons); // always compares 0 nowFinalize the max first, then reset.
Mistake 5: Returning currCons instead of maxCons.
On arrays that are all ones with no zeros, the finalization block only fires once at the last index. On arrays that end with a zero, it fires at the last zero but currCons is 0 afterward. Returning currCons instead of maxCons gives the wrong answer in both cases unless they happen to be equal.
Step 1: Name the two termination events before writing any code. Say: "A streak of ones ends in one of two ways: it hits a zero, or it reaches the last element of the array. Both events need to trigger the same finalization logic. Missing the second one is the most common bug on this problem."
Step 2: Describe the two variables and their roles precisely. Say: "currCons is the current active streak length. maxCons is the best finalized streak length. At any point, the true answer is max(maxCons, currCons), but we only update maxCons when a streak ends."
Step 3: Trace through a small example that ends with a streak. Use [1, 1, 0, 1, 1, 1] and show that the trailing three ones are correctly captured by the last-index check. This demonstrates you are aware of and have handled the critical edge case.
Step 4: Mention the ordering of operations inside the finalization block. Say: "I increment currCons before finalizing, so the last element is included in its streak. Then I update maxCons before resetting currCons, so I compare the completed streak, not the zeroed-out counter."
Step 5: Offer the post-loop alternative as a cleaner variant. Say: "An equally correct approach is to finalize inside the loop only on zeros, then add maxCons = Math.max(maxCons, currCons) after the loop as a single catch-all for any trailing streak. Some find that cleaner than the in-loop last-index check."
Track streaks, not totals. The problem is not asking how many ones are in the array. It is asking for the longest unbroken run. Counting all ones in a single pass would give the wrong answer for any input with zeros. The streak counter resets on zeros specifically because runs must be unbroken.
Always handle the final element explicitly. Any problem involving active state that gets updated during iteration carries this risk: the state built up by the last few elements may never be finalized if you only update on specific trigger conditions. A trailing finalization, whether inside the loop at the last index or outside the loop as a post-processing step, is one of the most common edge case fixes in array problems.
Order of operations inside a step matters. Increment before finalize. Finalize before reset. Getting these three actions in the wrong order produces answers that are off by one or that silently corrupt state. Write the order out explicitly when explaining your logic in an interview.
Simple problems still test iteration discipline. The code here is fewer than fifteen lines. But the bugs it is designed to catch, missing the trailing streak, resetting before finalizing, comparing the wrong value, are exactly the kind of subtle errors that appear under interview pressure. Easy problems measure precision, not just understanding.
Day 11 is done. A problem that fits on a single screen turned out to have a non-obvious edge case that catches most people on their first attempt. The array boundary is a termination event just like a zero, and treating both with equal care is what makes the solution correct.
On Day 12, we continue building. The streak-tracking instinct we developed today, tracking a running count, finalizing it at the right moment, and resetting cleanly, is a building block that appears in many problems beyond binary arrays.
If the two-termination-event framing helped you think about this more clearly, or if you want to see how today's logic extends to the flip-a-zero variant in LeetCode 487, drop a comment below.
See you on Day 12. 🚀
This post is part of my 250 Days DSA Challenge, solving one problem every day with a focus on intuition, patterns, and interview ready thinking.
When the window size is fixed, you never need to recompute the sum from scratch. Add one element, remove one element, keep sliding. Here is the full breakdown of the fixed-window sliding technique.
Water does not care about its neighbors. It cares about boundaries. Once you understand that, the prefix max approach becomes obvious. Full dry run, complexity analysis, and interview tips inside.
Sign in to join the discussion.
Rahul Kumar