Count 1's in a Sorted Binary Array looks trivial — but it is a clean lesson in turning "count something" into "find a boundary," and then attacking that boundary with binary search instead of a linear scan.
Problem. You are given a sorted binary array a — every 1 comes before every 0, like
[1, 1, 1, 1, 0, 0, 0]. Return how many 1s it contains.
Example: a = [1, 1, 1, 1, 0, 0, 0] → answer 4 (the first four elements are 1s).
The slow way first
The obvious idea: walk the array from the left and count 1s until you hit a 0. That works and is O(n) — fine for a small array, but it ignores the gift the problem hands us. The array is sorted: all the 1s are bunched on the left and all the 0s on the right. Whenever data is sorted, a linear scan is usually leaving speed on the table.
The key reframe: the number of 1s is exactly the index of the first 0 (the spot where 1 flips to 0). So we do not need to count anything — we just need to locate that 1→0 boundary.
The idea: binary-search the boundary
Finding the first 0 in a sorted array is a textbook binary search. Keep a window [lo, hi] and a running answer boundary (start it past the end, as if no 0 exists yet). Look at the middle:
- If
a[mid] == 0, this0might be the first one — recordboundary = mid, then search left for an even earlier0. - If
a[mid] == 1, the first0must be to the right — movelopastmid.
When the window is empty, boundary holds the index of the leftmost 0, which is the count of 1s.
The trick is that finding a[mid] == 0 does not stop the search — a later 0 is never the answer, but an earlier one might be, so we keep shrinking toward the left edge of the zeros.
Walk through it
Step through the animation on [1, 1, 1, 1, 0, 0, 0]. The pointers lo and hi bound the part still in play; mid is the cell we test. Each time we see a 1, the left half (dimmed) is discarded. Each time we see a 0, we record the boundary and hunt left. The search converges on index 4 — the first 0 — and that index, 4, is the count of 1s.
Pseudocode
lo, hi = 0, len(a) - 1
boundary = len(a) # index of first 0, assume none yet
while lo <= hi:
mid = (lo + hi) // 2
if a[mid] == 0:
boundary = mid # candidate first 0; look further left
hi = mid - 1
else: # a[mid] == 1
lo = mid + 1 # first 0 is to the right
return boundary # = number of 1sThe Python solution
def count_ones(a):
lo, hi = 0, len(a) - 1
boundary = len(a)
while lo <= hi:
mid = (lo + hi) // 2
if a[mid] == 0:
boundary = mid
hi = mid - 1
else:
lo = mid + 1
return boundaryboundarystarts atlen(a)— if the array were all1s, no0is ever found and the answer is the full length.mid = (lo + hi) // 2picks the middle of the live window.- Lines 6–8 are the heart: a
0atmidis a candidate first0, so we save it and pushhileft to look for an earlier one. - The
elsebranch handles a1atmid: the first0is strictly to the right, solo = mid + 1. - When the loop ends,
boundaryis the index of the leftmost0, which equals the number of1s.
Complexity
| Case | Time | Notes |
|---|---|---|
| Linear scan | O(n) (moderate) | count 1s until the first 0 |
| Binary search (this solution) | O(log n) (fast) | halve the window each step |
O(1) (fast)We turn an O(n) count into an O(log n) boundary search using only a constant amount of extra space. The win grows with the array: a million elements take about 20 comparisons instead of up to a million.
When this pattern shows up
When an array is sorted and the question is "how many satisfy a condition" or "where does the value change," reframe it as find the boundary and binary-search it. First-true / last-true searches power "first bad version," "search insert position," and "find the peak" — all the same move.
Recording the boundary is not the same as returning early. When a[mid] == 0 you must keep searching
left — an earlier 0 would be a smaller (correct) answer. Stopping at the first 0 you stumble on can
overcount the 1s.
Practice
For a = [1, 1, 0, 0, 0], the search tests mid = 2 first. What is a[2], and which way does the window move?
1. Why does the count of 1s equal the index of the first 0?
2. When a[mid] == 0, why do we keep searching to the left instead of returning mid?
3. Why is boundary initialized to len(a)?
4. What is the time complexity of the binary-search solution?