O(log N), O(sort)
The pure logarithmic-time family: classic search, the bisect boundary toolkit, and the dozens of variants that are really just one of those templates with a twist. When the input isn't sorted, an O(N log N) sort first still keeps us in this bucket.
The Classic Binary Search Algorithm
The single lower-bound template that solves almost everything else on this page.
704. Binary Search
This is the one template that solves almost every binary search problem - the lower-bound form. Instead of the classic three-way == / < / > split, it collapses the search to a single invariant: keep shrinking [lo, hi] until lo == hi, then check the survivor.
Invariant:
- The answer always lives in the closed range
[lo, hi]. - The loop condition is
lo < hi(not<=) - it stops the moment a single candidate remains, somidnever needs a separate "found" branch.
The decision:
mid = lo + (hi - lo) // 2is the left-biased midpoint (avoids overflow, always< hi).- If
nums[mid] < target, the answer must be strictly to the right ->lo = mid + 1. - Otherwise (
nums[mid] >= target),midcould still be the answer ->hi = mid(nevermid - 1).
When the loop ends, lo points at the leftmost index whose value is >= target. A final equality check confirms whether that survivor is actually the target.
- Time
- O(log N)
Nis the number of elements in the array.- Each iteration halves the search range, so the loop runs at most
log2(N)times. - Space
- O(1)
- Only the
lo,hi, andmidindices are tracked, independent of input size.
702. Search in a Sorted Array of Unknown Size
This is still the standard lower-bound binary search, but the array length is hidden behind an ArrayReader interface - we can only call reader.get(i), and out-of-range indices return a sentinel large value. So the problem splits into two phases.
Phase 1 - locate a boundary by doubling:
- Start with a tiny window
lo, hi = 0, 1. - While
reader.get(hi)is still smaller thantarget, the target lies further right, so jump the window forward:lo = hithenhi <<= 2(multiply by four). - Doubling (here quadrupling) grows the window exponentially, so after
O(log T)steps the target is guaranteed to sit inside[lo, hi], whereTis the target's index.
Phase 2 - ordinary binary search:
- With a valid
[lo, hi]range, run the familiar lower-bound loop:mid = lo + (hi - lo) // 2, movelo = mid + 1whenreader.get(mid) < target, otherwisehi = mid. - When the loop ends,
lois the leftmost index whose value is>= target; a final equality check confirms whether it is actually the target.
- Time
- O(2 log T)
Tis the index of the target value.- The boundary-doubling phase takes
O(log T)jumps, and the binary search over the found range is a separateO(log T)pass - two same-order phases,O(2 log T). - Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
74. Search a 2D Matrix
The matrix is row-sorted and each row's first value exceeds the previous row's last value - which means the row-major flattening of the matrix is itself one fully sorted 1D array. So we can run a single binary search over the virtual range [0, rows * cols - 1] without ever materializing the flat array.
The trick:
- A flat offset
imaps back to a cell withmatrix[i // col][i % col]- integer division picks the row, the remainder picks the column. - With that accessor, the problem collapses to the exact lower-bound template from 704: shrink
[lo, hi]until one candidate remains, then check equality.
- Time
- O(log(M·N))
MandNare the row and column counts.- Binary search runs over
M·Nvirtual cells, halving the range each step. - Space
- O(1)
- The flat array is never built; only
lo,hi, andmidoffsets are stored.
Bisect Algorithms
Four reusable insert-position primitives. Everything in the next sections calls one of these.
Bisect Left
bisect_left returns the position at which target should be inserted to keep the array sorted, choosing the leftmost valid slot when duplicates of target are already present. It is the lower-bound form of binary search returning an index rather than a boolean.
What the answer means:
- If
targetis present, the result is the index of its first (leftmost) occurrence. - If
targetis smaller than every element, the result is0. - If
targetis greater than every element, the result islen(arr)(one past the end).
How it works:
hiarrives exclusive and is turned into an inclusive boundhi = (hi or len(arr)) - 1.- An empty range (
lo == hi + 1) returns-1. - A quick check
arr[hi] < targetshort-circuits the "greater than all" case tohi + 1. - The core loop runs the standard lower-bound invariant: when
arr[mid] < targetmove right (lo = mid + 1), otherwise keepmidas a candidate (hi = mid).
- Time
- O(log N)
Nis the number of elements in the array.- Each iteration halves the candidate range.
- Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
Bisect Left Reverse
bisect_left_rev is the lower-bound insert for a descending array. It mirrors bisect_left, but every comparison flips direction because larger values now sit to the left.
What the answer means:
- If
targetis present, the result is the index of its leftmost occurrence within the descending order. - If
targetis larger than every element, the result is0. - If
targetis smaller than every element, the result islen(arr).
How it works:
hiarrives exclusive and becomes inclusive viahi = (hi or len(arr)) - 1.- An empty range (
lo == hi + 1) returns-1. - The short-circuit becomes
arr[hi] > target(the descending equivalent of "beyond all elements"). - In the loop the decision flips: when
arr[mid] > targetmove right (lo = mid + 1), otherwise keepmid(hi = mid).
- Time
- O(log N)
Nis the number of elements in the array.- Each iteration halves the candidate range.
- Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
Bisect Right
bisect_right returns the position at which target should be inserted to keep the array sorted, choosing the rightmost valid slot when duplicates of target are already present. It is the upper-bound form of binary search.
What the answer means:
- If
targetis present, the result is one past its last (rightmost) occurrence. - If
targetis smaller than every element, the result is0. - If
targetis greater than every element, the result islen(arr).
How it works:
histays inclusive here (hi = hi or len(arr)), because the answer can legitimately belen(arr).- An empty range (
lo == hi) returns-1. - A short-circuit
arr[hi - 1] < targethandles the "greater than all" case by returninghi. - The key difference from
bisect_left: we advanceloeven when equal (arr[mid] <= target), so the search slides past every duplicate to the rightmost boundary.
- Time
- O(log N)
Nis the number of elements in the array.- Each iteration halves the candidate range.
- Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
Bisect Right Reverse
bisect_right_rev is the upper-bound insert for a descending array. It mirrors bisect_right, but every comparison flips direction because larger values now sit to the left.
What the answer means:
- If
targetis present, the result is one past its rightmost occurrence within the descending order. - If
targetis larger than every element, the result is0. - If
targetis smaller than every element, the result islen(arr).
How it works:
histays inclusive (hi = hi or len(arr)), since the answer can belen(arr).- An empty range (
lo == hi) returns-1. - The short-circuit becomes
arr[hi - 1] > target, returninghi. - The descending decision advances
loeven when equal (arr[mid] >= target), sliding past every duplicate to the rightmost boundary.
- Time
- O(log N)
Nis the number of elements in the array.- Each iteration halves the candidate range.
- Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
Bisect Derivatives
One bisect call, lightly post-processed.
744. Find Smallest Letter Greater Than Target
We want the smallest letter strictly greater than target in a sorted letters array, wrapping back to the front if target is at or past the last letter. Because duplicates are allowed, we must glide past every copy of target and land just to its right - exactly what bisect_right does.
The two steps:
bisect_right(letters, target)returns the index one past the rightmost occurrence oftarget(or the lower bound whentargetis absent), which is the first letter strictly greater thantarget.- That index can equal
len(letters)whentargetis at or beyond the last letter, soindex % len(letters)wraps around to the first element.
- Time
- O(log N)
Nis the number of letters.- A single
bisect_rightruns in logarithmic time. - Space
- O(1)
- Only scalar indices are tracked.
35. Search Insert Position
Given a sorted array with distinct values, return the index of target if present, otherwise the index where it would be inserted to keep the array sorted. Since duplicates are not allowed, the leftmost and rightmost insert positions coincide, so a single bisect_left answers the question directly.
Why bisect_left:
- When
targetis present,bisect_leftreturns its exact index. - When
targetis absent, it returns the first index whose value is>= target- precisely the slot wheretargetbelongs. - If
targetexceeds every element, the result islen(nums)(insert at the end).
- Time
- O(log N)
Nis the number of elements innums.- A single
bisect_leftruns in logarithmic time. - Space
- O(1)
- Only scalar indices are tracked.
362. Design Hit Counter
We design a counter that records hit timestamps and reports how many hits happened in the past 300 seconds (the window [timestamp - 299, timestamp]). Because timestamps arrive in chronological order, the internal hits list is always sorted, which lets us answer each query with binary search instead of a scan.
The operations:
hit(timestamp)simply appends toself.hits. Since timestamps are monotonically non-decreasing, the list stays sorted - anO(1)operation.getHits(timestamp)finds the first hit still inside the window usingbisect_left(self.hits, timestamp - 300 + 1). Everything from that index to the end of the list is within the last 300 seconds, so the count islen(self.hits) - index.
Why bisect_left:
- It returns the leftmost index whose value is
>= timestamp - 299, i.e. the oldest hit that still counts. Hits older than the window sit to its left and are excluded.
This is a stateful design class whose behavior depends on a sequence of method calls, so the playground replays a scripted sequence of hit/getHits calls and shows the timestamp list growing and the binary-search window sliding.
- Time
- O(log N)
hitisO(1)- a single append, and the list stays sorted because timestamps are monotonically increasing.getHitsisO(log N)- it locates the window start with binary search, which dominates the class's worst-case per-call cost.- Space
- O(N)
Nis the number of recorded hits stored inself.hits.
Bisect Left & Right
Call both boundaries and work with the [left, right) span between them.
34. Find First and Last Position of Element in Sorted Array
The whole range of target occupies a contiguous block in the sorted array. Pin down its two edges with two lower-bound searches and the answer falls out.
Two boundary searches:
bisect_leftreturns the leftmost index wheretargetcould sit - the first occurrence iftargetexists.bisect_rightreturns the index one past the last occurrence oftarget.
Reading the result:
- When
targetis absent, both searches land on the same insertion point, soleft == right. That single equality covers every miss: below the smallest element, above the largest, or in a gap. - When
targetis present,[left, right - 1]is exactly the inclusive first/last index pair.
- Time
- O(log N)
Nis the number of elements in the array.- Both
bisect_leftandbisect_rightrun a halving loop, so each takeslog Nsteps. - Space
- O(1)
- Only scalar indices are tracked, independent of input size.
2089. Find Target Indices After Sorting Array
After sorting, every copy of target lands in one contiguous block. The "target indices" are exactly the positions of that block, so we just need its two edges.
Sort, then bound:
nums = sorted(nums)puts all equal values together.bisect_leftgives the index of the firsttarget.bisect_rightgives the index one past the lasttarget.
Building the answer:
range(left, right)enumerates every index occupied bytarget.- If
targetis absent,left == right, so the range is empty and the result is[].
- Time
- O(2 log N + N log N)
Nis the number of elements in the array.sorted(nums)costsN log N, thenbisect_leftandbisect_righteach binary search the sorted array inlog N- two separateO(log N)passes plus the sort,2 log N + N log N.- Space
- O(sort + N)
sorted(nums)allocates a new list ofNelements, andlist(range(left, right))holds at mostNindices.- Sorting algorithms are typically
O(log n)space (in-place, recursion stack only), but Python'slist.sort()(used internally bysorted()) is Timsort, which allocates up toO(n)auxiliary space in the worst case - that's whatsortstands for here.
1150. Check If a Number Is Majority Element in a Sorted Array
A value is the majority element when it occupies more than half the array. In a sorted array every copy of target is contiguous, so counting them is just measuring the width of that block.
Bound the block:
bisect_leftgives the index of the firsttarget.bisect_rightgives the index one past the lasttarget.count = right - leftis how many timestargetappears.
The majority test:
- Return
count > len(nums) / 2. Iftargetis absent the block is empty, socount == 0and the test fails naturally.
- Time
- O(log N)
Nis the number of elements in the array.- Both
bisect_leftandbisect_rightrun a halving loop, so each takeslog Nsteps. - Space
- O(1)
- Only scalar indices and a count are tracked.
Element Occurrence
The number of times target appears in a sorted array is the width of its contiguous block. Find the two edges of that block and subtract.
Bound the block:
bisect_leftgives the index of the firsttarget.bisect_rightgives the index one past the lasttarget.count = right - leftis the number of occurrences.
Reporting absence:
- When
targetis missing both boundaries coincide, socount == 0; the solution returns-1to signal "not found".
- Time
- O(2 log n)
n = len(nums).findCountcallsbisect_leftandbisect_rightonce each, and both run their own halvingwhile lo < hiloop - two separateO(log n)searches, giving2 log n.- Space
- O(1)
- Only scalar indices (
left,right,lo,hi,mid) andcountare tracked.
Slight Variants
The classic template with the comparison swapped for a derived predicate.
1064. Fixed Point
1064Fixed Point
A fixed point is an index where arr[i] == i, and we want the smallest such index. Because the array is sorted with distinct integers, the quantity arr[i] - i is non-decreasing, which makes this a lower-bound search.
The decision:
- If
arr[mid] < mid, the value is still lagging behind its index, so any fixed point must lie strictly to the right ->lo = mid + 1. - Otherwise (
arr[mid] >= mid),midcould be the first index where the value catches up to its index ->hi = mid.
Confirming the survivor:
- When the loop ends,
lois the leftmost candidate. Return it only ifarr[lo] == lo; otherwise there is no fixed point and we return-1.
- Time
- O(log N)
Nis the number of elements in the array.- Each iteration halves the search range, so the loop runs at most
log2(N)times. - Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
374. Guess Number Higher or Lower
The search space is the integer range [0, n], and the pre-supplied guess API replaces a direct array comparison. The lower-bound template still applies cleanly.
The probe:
guess(mid)returns1when our pick is too low, and-1or0when it is too high or exactly right.guess(mid) == 1means the target lies strictly to the right ->lo = mid + 1.- Otherwise
midcould be the answer ->hi = mid.
Termination:
- The loop runs while
lo < hi, collapsing to the single surviving candidate, which is the picked number.
- Time
- O(log N)
Nis the size of the guessing range[1, n].- Each call to
guesshalves the remaining range. - Space
- O(1)
- Only the
lo,hi, andmidvalues are tracked.
278. First Bad Version
Versions are monotone: once a version is bad, every later version is bad too. That turns the problem into finding the leftmost True in a False...False True...True sequence - exactly the lower-bound template, with the isBadVersion API standing in for an array read.
The probe:
isBadVersion(mid) == Falsemeans every bad version is strictly to the right ->lo = mid + 1.- Otherwise
midis bad and could be the first one ->hi = mid.
Termination:
- When
lo == hi, the survivor is the first bad version.
- Time
- O(log N)
Nis the number of versions.- Each
isBadVersioncall halves the candidate range. - Space
- O(1)
- Only the
lo,hi, andmidvalues are tracked.
441. Arranging Coins
A full staircase of k rows uses coins(k) = k * (k + 1) / 2 coins. We want the largest k whose staircase still fits inside n, so we binary search the row count over [0, n].
The probe:
coins(mid) <= nmeansmidrows fit, so a taller staircase might also fit ->lo = mid + 1.- Otherwise
midoverflows ->hi = mid.
Reading the survivor:
- After the loop
lois the first row count that is too tall (or exactlyn). - If
coins(lo) == nit is a perfect fit; otherwise the last complete row count islo - 1.
- Time
- O(log n)
nis the given coin count.- The
while lo < hiloop halves the[lo, hi]range of candidate row counts each iteration:O(log n). - Space
- O(1)
- Only the
lo,hi, andmidvalues are tracked.
287. Find the Duplicate Number
The values lie in the range [1, n], so in a clean array of length n the element at index i would be exactly i + 1. A duplicate breaks this alignment and shifts everything after it, which makes the mismatch detectable by a lower-bound search.
Sort and compare to expected:
nums = sorted(nums)arranges the values in order.expected(i)is the value that indexiwould hold with no duplicates:i + 1.
The decision:
- If
expected(mid) == nums[mid], no duplicate has appeared at or beforemid, so it must lie to the right ->lo = mid + 1. - Otherwise the alignment has already broken ->
hi = mid.
When the loop ends, lo is the first index where the value no longer matches its expected slot, and nums[lo] is the repeated number.
- Time
- O(log N + N log N)
Nis the number of elements in the array.sorted(nums)costsO(N log N), and thewhile lo < hibinary search that follows adds its ownO(log N)pass over the sorted array - a distinct, slower-growing term that doesn't collapse into the sort.- Space
- O(sort + N)
sorted(nums)allocates a new list of sizeN.- Sorting algorithms are typically
O(log n)space (in-place, recursion stack only), but Python'slist.sort()/sorted()is Timsort, which allocates up toO(n)auxiliary space in the worst case - that's whatsortstands for here.
Single Element in a Sorted Array
Every element appears twice except one, so the array always has an odd length. The lone element is detectable by the parity of the segment to the right of mid: a clean run of pairs has an even count, and the single element flips that parity.
Aligning mid to a pair boundary:
- If
nums[mid] == nums[mid + 1],midis the left half of a pair. Step it back withmid -= 1so the pair is counted whole and the parity argument stays valid.
The decision:
right_len = hi - midmeasures the right segment.- If
right_lenis odd, the single element lies to the right ->lo = mid + 1. - If it is even, the single element is at
midor to its left ->hi = mid.
When the loop ends, lo points at the unique element.
- Time
- O(log n)
nis the number of elements innums.- The
while lo < hiloop halves the[lo, hi]range each iteration, so it runs at mostlog2(n)times. - Space
- O(1)
- Only the
lo,hi,mid, andright_lenscalars are tracked.
Math - Square
Binary search over a numeric answer range to invert a monotone math function.
69. Sqrt(x)
69Sqrt(x)
The integer square root is the largest k with k * k <= x. We binary search k over [0, x] using the lower-bound template.
The probe:
square(mid) <= xkeepsmidas a valid candidate and pushes for a larger root ->lo = mid + 1.- Otherwise
mid * midovershoots ->hi = mid.
Reading the survivor:
- The
<=test meanslolands on the first integer whose square is strictly greater thanx. - If
square(lo) == xit is a perfect square; otherwise the floor of the root islo - 1.
- Time
- O(log N)
Nis the given numberx.- Each iteration halves the candidate range of roots.
- Space
- O(1)
- Only the
lo,hi, andmidvalues are tracked.
367. Valid Perfect Square
This mirrors finding an integer square root: binary search the candidate root k over [0, num], then check whether the floor of the root squares back to num.
The probe:
square(mid) <= numkeepsmidvalid and reaches for a larger root ->lo = mid + 1.- Otherwise
mid * midovershoots ->hi = mid.
The final check:
lolands one past the floor root, so reduce it to the floor:lo if square(lo) == num else lo - 1.- The answer is whether that floor root squared equals
num.
- Time
- O(log num)
numis the input value. Eachwhile lo < hiiteration comparessquare(mid)againstnumand halves the[lo, hi]candidate range.- Space
- O(1)
- Only the
lo,hi, andmidvalues are tracked.
633. Sum of Square Numbers
We want non-negative integers a and b with a*a + b*b == c. Once a is fixed, b*b is forced to be c - a*a, so the only freedom is the choice of a.
Enumerate a:
- Since
a*a <= c, it is enough to try everyain[0, sqrt(c)]. - For each
a, compute the requiredb = c - a*a.
Test the complement:
bis a valid square iff it is a perfect square, which the foldedisPerfectSquarehelper decides by binary searching its integer root.- If any complement is a perfect square, the answer is
True; otherwiseFalse.
- Time
- O(sqrt(c) * log c)
cis the given number.- The loop runs
O(sqrt(c))times, and each perfect-square check isO(log c). - Space
- O(1)
- Only scalar values are tracked.
Missing Elements
Bisect on a "how many are missing so far" predicate.
268. Missing Number
The array holds n distinct values drawn from [0, n], so exactly one value is missing. After sorting, a complete prefix satisfies nums[i] == i; the missing number is the first index where that equality breaks.
Invariant:
- For every index before the gap,
nums[mid] == mid, somid >= nums[mid]. - At and after the gap, values are shifted up by one, so
mid < nums[mid].
The decision:
mid >= nums[mid]means the prefix is still intact, so the gap is to the right ->lo = mid + 1.- Otherwise the gap is at or before
mid->hi = mid.
The hi pointer:
histarts atlen(nums)because the missing value can benitself (when0..n-1are all present). Thehimarker may render one slot past the array, which is expected.
- Time
- O(log n + n log n)
nis the number of elements. The binary search overlo/hi/midis a separateO(log n)phase from theO(n log n)sort - the sort dominates but the binary search is still its own pass.- Space
- O(sort)
- Only the
lo,hi, andmidindices are tracked beyond 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.
1060. Missing Element in Sorted Array
In a gap-free run starting at nums[0], index i would hold nums[0] + i. The number of values missing up to index i is therefore missing(i) = nums[i] - nums[0] - i, a non-decreasing quantity. We binary search for the first index where the running count of missing numbers reaches k.
The decision:
missing(mid) < kmeans the k-th missing value still lies further right ->lo = mid + 1.- Otherwise the count is at least
k, so the boundary is at or beforemid->hi = mid.
Reading the answer:
- After the loop, index
lo - 1is the last position before the count reachesk. - The k-th missing number is
nums[lo - 1]plus the remaining shortfallk - missing(lo - 1).
The hi pointer:
histarts atlen(nums)because the answer can exceednums[-1]. Thehimarker may render one slot past the array, which is expected.
- Time
- O(log N)
Nis the number of elements.- Each iteration halves the candidate index range.
- Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
1539. Kth Missing Positive Number
The positive integers start at 1, so in a gap-free array index i would hold i + 1. The number of positives missing up to index i is missing(i) = arr[i] - 1 - i, a non-decreasing quantity. We binary search for the first index where that count reaches k.
The decision:
missing(mid) < kmeans the k-th missing positive is still further right ->lo = mid + 1.- Otherwise the count is at least
k, so the boundary is at or beforemid->hi = mid.
Reading the answer:
- Index
lo - 1is the last position before the count hitsk. - The answer is
arr[lo - 1]plus the leftover shortfallk - missing(lo - 1).
The hi pointer:
histarts atlen(arr)because the answer can exceedarr[-1]. Thehimarker may render one slot past the array, which is expected.
- Time
- O(log N)
Nis the number of elements.- Each iteration halves the candidate index range.
- Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
Rotated Sorted Array
Locate the pivot (minimum), then search the right half.
Find the Pivot (Minimum)
A rotated sorted array is just a sorted array cut at one point and swapped, so the pivot (the minimum) is the only place where the order breaks. This template finds that pivot even when duplicates are present.
The comparison anchor:
- We never compare against
nums[lo]to decide where to move - we comparenums[mid]againstnums[hi]. The right end is a reliable reference for the lower-bound invariant.
The three cases:
- If
nums[hi] < nums[mid], the dip is strictly to the right ofmid->lo = mid + 1. - If
nums[hi] > nums[mid],midcould still be the minimum ->hi = mid. - If they are equal we cannot tell which side holds the pivot, so we shrink the window conservatively with
hi -= 1. This is what makes duplicates safe (at the cost ofO(n)in the worst case).
When the loop ends, lo == hi points at the minimum element's index.
- Time
- O(log N) avg, O(N) worst
Nis the number of elements in the array.- Each iteration normally halves the range. With many duplicates the
hi -= 1branch only shrinks by one, degrading toO(N). - Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
154. Find Minimum in Rotated Sorted Array II
The minimum of a rotated sorted array sits exactly at the pivot - the point where the ascending order breaks. Once we know the pivot's index, the answer is just the value there.
Reusing the pivot finder:
findPivotruns the lower-bound search comparingnums[mid]against the right endnums[hi], returning the index of the minimum.findMinsimply returnsnums[pivot].
Why duplicates make this Hard:
- When
nums[mid] == nums[hi]we cannot decide which half holds the minimum, so the pivot finder falls back tohi -= 1. This keeps correctness but degrades the worst case toO(N)(e.g. an array of all equal values).
- Time
- O(log n) avg, O(n) worst
nislen(nums).- When
nums[hi] != nums[mid], each step offindPivothalves the[lo, hi]range:O(log n). - When
nums[hi] == nums[mid], thehi -= 1fallback only shrinks the range by one, which can degrade the whole search toO(n)(e.g. an array of all equal values). - Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
153. Find Minimum in Rotated Sorted Array
With unique elements, finding the minimum of a rotated sorted array collapses to a clean two-way lower-bound search. The minimum is the only element smaller than its predecessor - the pivot where the rotation wraps.
The comparison anchor:
- We compare
nums[mid]against the right endnums[hi], never the left. - If
nums[mid] > nums[hi], the dip is strictly to the right ->lo = mid + 1. - Otherwise
midcould still be the minimum ->hi = mid.
No duplicate fallback needed:
- Because all values are distinct, the equal-to case never happens, so we drop the
hi -= 1branch entirely. That keeps the search strictlyO(log N). When the loop ends,nums[lo]is the minimum.
- Time
- O(log n)
n = len(nums). Eachwhile lo < hiiteration comparesnums[mid]againstnums[hi]and halves the[lo, hi]range.- Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
33. Search in Rotated Sorted Array
A rotated sorted array is two ascending runs glued together at the pivot (the minimum). If we locate that pivot, the array becomes two ordinary sorted segments and a plain binary search handles each.
Step 1 - find the pivot:
findPivotreturns the index of the minimum, which is also the boundary between the two ascending runs.
Step 2 - search both halves:
- Run
bisect_lefton the left run[0, pivot); if the landing index holdstarget, return it. - Otherwise run
bisect_lefton the right run[pivot, len(nums))and check again. - If neither lands on
target, it is absent -> return-1.
Each binary search is O(log N), so even with the pivot pass the whole thing stays logarithmic.
- Time
- O(log N)
Nis the number of elements in the array.- One pivot search plus two bounded binary searches, each
O(log N). - Space
- O(1)
- Only index scalars are tracked.
Peaks in an Array
Walk uphill: compare the midpoint to its neighbor and move toward the larger side.
162. Find Peak Element
A peak is any element strictly greater than both neighbors, and the boundaries are treated as -infinity. We don't need to see the whole array - we only need to follow the local slope uphill, which binary search can do.
The slope test:
- Compare
nums[mid]with its right neighbornums[mid + 1]. - If
nums[mid] < nums[mid + 1], the slope rises to the right, so a peak must exist there ->lo = mid + 1. - Otherwise the slope falls (or
midis itself a peak), so a peak lies atmidor to its left ->hi = mid.
Because lo < hi keeps mid + 1 in range, this never reads out of bounds. When the loop ends, lo indexes a valid peak.
- Time
- O(log N)
Nis the number of elements in the array.- Each iteration discards half the range.
- Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
852. Peak Index in a Mountain Array
A mountain array strictly rises to a single peak and then strictly falls. This is a special case of finding any peak - there is exactly one - so the same slope-following binary search applies.
The slope test:
- Compare
arr[mid]with its right neighborarr[mid + 1]. - If
arr[mid] < arr[mid + 1], we are still on the ascending side, so the peak is to the right ->lo = mid + 1. - Otherwise we are on (or at the top of) the descending side ->
hi = mid.
Since the mountain guarantees a single peak, the loop converges to its index in lo.
- Time
- O(log N)
Nis the number of elements in the array.- Each iteration discards half the range.
- Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
1095. Find in Mountain Array
A mountain array rises to one peak and then falls. Finding a target splits into locating that peak and then binary-searching each monotone slope - the ascending left side normally, the descending right side in reverse.
Step 1 - find the peak:
peakIndexInMountainArrayreturns the index of the single maximum, the boundary between the two runs.
Step 2 - search the ascending half:
- Run
bisect_leftover[0, peakIndex); if it lands ontarget, that is the leftmost (and answer-priority) match.
Step 3 - search the descending half:
- Run
bisect_left_rev(the reverse-order variant) over[peakIndex, len - 1]; if it lands ontarget, return it. - If neither side matches, the target is absent -> return
-1.
The wrapper trick:
- The judge passes a
MountainArrayobject that only exposes.get(i)and.length(), not normal indexing.CustomMountainArraywraps it solen(...)andarr[i]work, letting the genericbisect_left,bisect_left_rev, and peak finder be reused unchanged.
- Time
- O(log N)
Nis the number of elements in the array.- One peak search plus two bounded binary searches, each
O(log N). - Space
- O(1)
- Only the wrapper handle and index scalars are tracked.
H-Index
Find the crossover point where citations meet the rank-from-the-end count.
275. H-Index II
275H-Index II
With citations already sorted ascending, the h-index is the largest h such that at least h papers have >= h citations. At index i there are exactly len(citations) - i papers from i to the end, so we binary-search for the crossover point.
The helper:
cited = lambda i: len(citations) - iis the count of papers at indexior later - the candidate h-value.
The decision:
- If
cited(mid) > citations[mid], this paper's citation count can't support that many papers, so the valid h must come from the right ->lo = mid + 1. - Otherwise
midis still feasible ->hi = mid.
After the loop, lo is the crossover index. We return cited(lo) if citations[lo] actually supports it, else 0.
- Time
- O(log N)
Nis the number of papers (length ofcitations).- The array is pre-sorted, so a single binary search suffices.
- Space
- O(1)
- Only the
lo,hi, andmidindices are tracked.
274. H-Index
274H-Index
The h-index is the largest h such that at least h papers have >= h citations. The only difference from the sorted variant is that the input arrives unsorted, so we sort first and then run the identical boundary search.
Sort first:
- After
sorted(citations), the array is ascending and the index-based counting argument holds.
The helper:
cited = lambda i: len(citations) - iis the number of papers from indexionward - the candidate h-value at that position.
The decision:
- If
cited(mid) > citations[mid], that paper can't back this many papers ->lo = mid + 1. - Otherwise
midstays feasible ->hi = mid.
After the loop, return cited(lo) if citations[lo] supports it, else 0.
- Time
- O(log N + N log N)
Nis the number of papers (length ofcitations).- The
while lo < hibinary search halves its range each step -O(log N)- on top of theO(N log N)sort. - Space
- O(sort + N)
citations = sorted(citations)builds a brand-new list ofNelements, on top of the sort's own working memory.- Sorting algorithms are typically
O(log N)space (in-place, recursion stack only), but Python'ssorted()is Timsort, which allocates up toO(N)auxiliary space in the worst case - that's whatsortstands for here.