Secondary Loop BS
Binary search as the inner step of an outer loop. The outer pass walks elements (or rows); for each one we bisect into a sorted structure. Total cost is O(N log M) - the loop times the search.
Bisect Derivatives
Loop one value, binary-search for its partner (complement, double, or matching boundary).
167. Two Sum II - Input Array Is Sorted
The array is already sorted, so instead of an inner linear scan we can binary-search for each element's complement. Fix an outer index i, then look for the value that completes the pair.
The outer loop:
- For each
i, the partner we need isfind = target - numbers[i]. - Because every pair
(i, j)is symmetric, we only ever search the suffix to the right ofi- the range[i + 1, len(numbers)). This avoids re-finding pairs and keeps each search strictly forward.
The inner search:
bisect_leftreturns the leftmost insertion point forfindin that suffix.- If that landing index
jis in bounds andnumbers[j] == find, we found the pair and return the 1-indexed answer[i + 1, j + 1].
- Time
- O(n log n)
n = len(numbers). The outerfor iloop runsntimes, and each iteration callsbisect_left, anO(log n)binary search for the complement.- Space
- O(1)
- Only scalar indices (
i,lo,hi,mid) are tracked; no extra structures are built.
1855. Maximum Distance Between a Pair of Values
Both arrays are sorted in descending order, and a valid pair (i, j) needs i <= j with nums1[i] <= nums2[j]. We want to maximize j - i.
The outer loop:
- For each element
a = nums1[i], everynums2[j]that is>= aforms a valid pair. Becausenums2is descending, those validjform a prefix[0, boundary).
The inner search:
bisect_right_revfinds the insertion boundaryjforain the descendingnums2- i.e. how manynums2values are>= a.- The best distance for this
iisj - i - 1, and we keep the running maximum.
- Time
- O(M log N)
Mis the length ofnums1,Nis the length ofnums2.- We iterate over
nums1and binary-searchnums2each time, which costsO(log N). - Space
- O(1)
- Only scalar counters are tracked.
1346. Check If N and Its Double Exist
We need a pair where one value is exactly twice another. Sorting the array first lets us binary-search for each element's double.
The outer loop:
- After
arr.sort(), we walk every valuenum. - For each non-zero
num, we look for2 * numin the sorted array usingbisect_left. If the landing index is in bounds and holds2 * num, the pair exists and we returnTrue.
The zero case:
0is its own double, so a single zero is not enough - we need two of them. We count zeros separately and returnTrueifzeros >= 2.
- Time
- O(2 N log N)
Nis the length of the array.- Sorting is
O(N log N), and the loop runsNtimes with anO(log N)bisect_leftcall each, anotherO(N log N)- two same-order passes collapse to2 N log N. - Space
- O(sort)
zerosandindexare scalars; the only space beyond the input is the sort's own working memory.- Sorting algorithms are typically
O(log n)space (in-place, recursion stack only), but Python'slist.sort()is Timsort, which allocates up toO(n)auxiliary space in the worst case - that's whatsortstands for here.
Bisect Derivatives in a 2D Matrix
Each row is independently sorted, so bisect row by row.
1351. Count Negative Numbers in a Sorted Matrix
Each row is sorted in descending order, so within a row all the negatives sit in a contiguous suffix. The job per row is to find where that negative suffix begins.
The per-row search:
bisect_right_rev(grid[row], 0)returns the boundary index where0is supposed to be inserted in the descending row - equivalently, the first index where negatives start.- It works even when
0is absent: the boundary still lands exactly where the non-negative prefix ends.
Counting:
- The number of negatives in the row is
len(grid[row]) - index. Summing this over all rows gives the total.
- Time
- O(N log M)
Nis the number of rows,Mis the number of columns.- Each row gets one
O(log M)binary search, repeatedNtimes. - Space
- O(1)
- Only a running counter is tracked.
1337. The K Weakest Rows in a Matrix
Every row is 1s followed by 0s, so a row's strength is just its count of 1s. The "weakest" rows are those with the fewest soldiers, breaking ties by smaller index.
Counting soldiers per row:
- The
1s form a descending block (then0s), sobisect_left_rev(mat[row], 0)returns the boundary where0begins - exactly the number of1s in that row.
Keeping only K:
- Python only has a min-heap, so we push
(-soldiers, -row)to simulate a max-heap of strength. - Whenever the heap exceeds
k, we pop the strongest entry, leaving thekweakest behind. - Finally we drain the heap, flip the indices back to positive, and reverse so the result is ordered from weakest to strongest.
- Time
- O(N log(M·K))
Nis the number of rows,Mis the number of columns.- Each row costs
O(log M)to count soldiers andO(log K)for the heap operation, givingN·(log M + log K) = O(N log(M·K)). - Space
- O(K)
- The heap holds at most
kentries at any time.
Intersection
Iterate the first array (or row), and binary-search every other one for the same value.
349. Intersection of Two Arrays
The intersection is the set of values that appear in both arrays. Sort both, then walk the first array and binary-search each value in the second - a hit means the value belongs to the result.
The setup:
- Sort
nums1andnums2. Sorting both isO(N log N); it lets us binary-search the second array and skip duplicates in the first cheaply. - Iterate
nums1in order. Because it is sorted, every duplicate value sits next to its twin, so a singleprevguard collapses repeats - each distinct value is processed once.
The lookup:
- For each
num,bisect_left(nums2, num)returns the leftmost insert position. - If that index is in range and
nums2[index] == num, the value is present in both arrays, so append it toans.
The prev check is what keeps the output a true set: without it, a repeated value in nums1 would be added multiple times.
- Time
- O(N log N + M log M + N log M)
Nis the length ofnums1,Mis the length ofnums2.nums1.sort()costsO(N log N).nums2.sort()costsO(M log M).- The loop then walks the
Nvalues ofnums1and binary-searchesnums2(log Mperbisect_leftcall) for each -N log M. - Space
- O(sort)
- Beyond the sort's working memory, only the result list and a
prevscalar are kept. - Sorting algorithms are typically
O(log n)space (in-place, recursion stack only), but Python'slist.sort()is Timsort, which allocates up toO(n)auxiliary space in the worst case - that's whatsortstands for here.
350. Intersection of Two Arrays II
This is the multiset version of the intersection: a value that appears twice in both arrays should appear twice in the result. So instead of skipping duplicates, we consume each match from the second array.
The setup:
- Sort
nums1andnums2so binary search applies. Sorting isO(N log N). - Iterate every value of
nums1(noprevguard this time - repeats are intentional).
The lookup and consume:
bisect_left(nums2, num)finds the leftmost insert position.- If that index is in range and
nums2[index] == num, appendnumtoansandpopit out ofnums2. Removing the matched element means a later duplicate innums1can only match a still-unused element, giving correct multiplicities.
Cost note:
- The
pop(index)shifts the tail ofnums2, which isO(M)per match - so the worst case degrades toO(N*M)even though each lookup itself is logarithmic.
- Time
- O(n log n + m log m + n*m)
nums1.sort()andnums2.sort()costO(n log n)andO(m log m), wheren = len(nums1)andm = len(nums2).- The
for num in nums1loop runsntimes; each iteration callsbisect_left(O(log m)) and, on a match,nums2.pop(index), which shifts the tail ofnums2inO(m). In the worst case every iteration matches, so the loop costsO(n*m)(theO(log m)bisect is dominated by theO(m)pop within the same iteration). - Space
- O(sort + m)
ansholds at mostmin(n, m)matched values, bounded byO(m).- Sorting algorithms are typically
O(log n)space (in-place, recursion stack only), but Python'slist.sort()is Timsort, which allocates up toO(n)auxiliary space in the worst case - that's whatsortstands for here.
1198. Find Smallest Common Element in All Rows
Each row is sorted ascending, so the candidates for "smallest common element" are exactly the values of the first row, taken in increasing order. The first one that exists in every other row is the answer.
The outer scan:
- Walk
mat[0]left to right. Since it is sorted, the first value confirmed in all rows is automatically the smallest common element.
The inner probe:
- For each candidate
num, binary-search it in every other row withbisect_left(mat[row], num). - A row "contains"
numwhen the returned index is in range andmat[row][index] == num. The moment a row misses, setfound = Falseandbreak- no point checking the rest.
Result:
- If a candidate clears all rows, return it immediately. If the first row is exhausted with no winner, return
-1.
- Time
- O(N*M log M)
Nis the number of rows,Mis the number of columns.- For each of the
Mfirst-row candidates, we binary-search (log M) across the otherNrows. - Space
- O(1)
- Only scalar bookkeeping (
num,found,index) is used.
1213. Intersection of Three Sorted Arrays
This is the list-returning sibling of "smallest common element". Stack the three sorted arrays as rows of a matrix, then collect every value of the first row that also appears in the other two.
The setup:
- Group the inputs as
mat = [arr1, arr2, arr3]. The first row (arr1) supplies the ordered candidate stream; since it is sorted, the collected answers come out sorted for free.
The inner probe:
- For each candidate
num, binary-search it in every other row withbisect_left(mat[row], num). - A row contains
numwhen the index is in range andmat[row][index] == num. The first miss flipsfound = Falseand breaks out early.
Result:
- Unlike 1198, we don't stop at the first hit - every candidate that survives all rows is appended to
ans, which is returned at the end.
- Time
- O(N*M log M)
Nis the number of arrays (here 3),Mis the length of the longest.- For each of the
Mcandidates from the first array, we binary-search (log M) across the other arrays. - Space
- O(1)
- Ignoring the output list, only scalar bookkeeping is used.
Custom Bisect
When the input is an opaque interface, hand-roll the bisect against its accessor.
1428. Leftmost Column with at Least a One
Each row of the binary matrix is sorted - all the 0s come before all the 1s. So within a row, the leftmost 1 is exactly a lower-bound of the value 1, which a standard binary search finds in O(log M).
Per-row bisect:
- A nested
bisect_left(row)runs the lower-bound template against the row, reading cells lazily throughbinaryMatrix.get(row, mid). It returns the first column whose value is>= 1, i.e. the first1.
The shrinking horizon:
- We track
mini, the best (smallest) column seen so far. For a new row, there is no reason to look pastmini- any1further right cannot improve the answer. The probe is therefore bounded by the running minimum, which keeps the total work nearO(N log M). - A quick guard
binaryMatrix.get(row, n - 1) != 0skips all-zero rows before bisecting.
Result:
- Return the smallest column that ever held a
1, or-1if no row contained one.
- Time
- O(N log M)
Nis the total number of rows,Mis the total number of columns.- Each row triggers one binary search over its columns, costing
log M. - Space
- O(1)
- Only scalar trackers (
mini,first_one, loop indices) are used.