Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
What is Binary Search?
Binary Search is a divide-and-conquer algorithm that repeatedly halves the search space. It reduces O(n) linear search to O(log n) by eliminating half the possibilities at each step. Imagine looking up a word in a physical dictionary. You would never start at page 1 and read every page. Instead, you open to the middle, decide if your word comes before or after that page, and repeat on the correct half. Each flip eliminates roughly half the remaining pages. That is binary search: a disciplined halving of possibilities. The key insight is that binary search does not require a “sorted array” specifically — it requires a monotonic predicate. If you can define a condition that isfalse for all values below some threshold and true for all values above it (or vice versa), you can binary-search for that threshold. This generalization is what enables “binary search on the answer” for optimization problems, which is arguably the most common interview pattern.
Complexity: O(log n) time, O(1) space. For a billion-element array, that is about 30 comparisons instead of a billion — one of the most dramatic efficiency wins in all of computer science.
Pattern Recognition Cheatsheet
Universal Templates
These three templates cover roughly 95% of binary search interview problems. The single most important insight: pick a template and stick to it for that problem — do not mix conventions.Template 1: Exact Match (left <= right)
Use when the answer is a specific element in a sorted array, and “not found” returns -1.
Template 2: Lower Bound — the canonical interview template (left < right)
Use this for first occurrence, insertion point, first element satisfying a predicate, and most “binary search on the answer” problems. The convergence is left == right == answer.
left < right and not left <= right here? Because right = mid (not mid - 1) keeps mid in the range. If you used left <= right with right = mid, then when left == right == mid, the next iteration sets right = mid again — no progress, infinite loop.
Why left = mid + 1 and not left = mid? Because mid was just shown to be too small (arr[mid] < target), so excluding it is safe. If you wrote left = mid, then at left + 1 == right, mid equals left, and you would loop forever.
The pattern is rigid: left < right, floor mid, right = mid when condition holds, left = mid + 1 when it does not. Memorize it.
Template 3: Upper Bound (left < right with strict comparison)
To find first element strictly greater than target:
last_occurrence(target) == upper_bound(target) - 1 (and verify the value matches).
How to choose mid for upper-bound (when left = mid is needed)
Some upper-bound flavors use left = mid instead of left = mid + 1 (when you want mid to potentially be the answer in the increasing-from-left direction). In that case, you MUST use ceiling division for mid:
left + 1 == right, mid equals left, and left = mid makes no progress — infinite loop. The ceiling shifts mid toward the right so the update always moves forward. This pattern is rare but appears in problems like “find the largest K such that condition” written with left = mid.
Pattern Recognition Checklist
Use Binary Search When
- Array is sorted (or has monotonic property)
- Need O(log n) instead of O(n)
- Minimize/maximize with feasibility check
- Finding transition point where condition changes
- Search space can be halved each step
Don't Use When
- Array is unsorted with no order
- Need to find all occurrences
- Linear scan is already O(n) and sufficient
- No clear way to eliminate half
When to Use
Sorted Arrays
Monotonic Functions
Optimization Problems
Hidden Sorted Space
Pattern Variations
1. Standard Binary Search
Find exact target in sorted array. The loop invariant is: “the target, if it exists, is within[left, right].” When left > right, the interval is empty and the target does not exist.
Why left + (right - left) // 2 instead of (left + right) // 2? In Java/C++, left + right can overflow a 32-bit integer when both are large. The subtraction form avoids this. In Python it does not matter (arbitrary precision), but using the safe form is good habit.
Time: O(log n). Space: O(1).
2. Lower Bound (First Occurrence)
Find the first index wherearr[i] >= target. Notice two critical differences from standard search: (1) right starts at len(arr) (one past the end, because the answer might be “insert at the end”), and (2) the loop condition is left < right (not <=), because left == right means we have converged on the answer.
When arr[mid] >= target, we do right = mid (not mid - 1) because mid itself might be the answer — we cannot discard it.
Use this for: finding insertion points, first occurrence of a value, first position meeting a condition.
3. Upper Bound (Last Occurrence)
Find the first index wherearr[i] > target. This is the mirror image of lower bound: instead of >=, we use >. The only code difference is changing < to <= in the comparison — but that single character changes the semantics entirely.
How it relates to lower bound: upper_bound(target) is the same as lower_bound(target + 1) for integers. For finding the last occurrence of a value, compute upper_bound(target) - 1 and verify that position holds the target.
Walked example: arr = [1, 3, 3, 3, 5, 7], target = 3. We want the first index where arr[i] > 3, which is index 4 (value 5). So upper_bound = 4. The last occurrence of 3 is at index 4 - 1 = 3.
Use this for: finding the last occurrence of a value (upper_bound - 1), counting occurrences (upper_bound - lower_bound), finding the range of equal elements.
4. Binary Search on Answer
This is arguably the most important binary search pattern for interviews. Instead of searching for an element in an array, you search for the optimal value in a continuous answer space. The pattern always has three parts:- Define the search space — what is the minimum and maximum possible answer?
- Write a feasibility function — given a candidate answer, can we satisfy the constraints?
- Binary search on the answer space using the feasibility function as the monotonic predicate.
Classic Problems
1. Search in Rotated Sorted Array
1. Search in Rotated Sorted Array
2. Find Peak Element
2. Find Peak Element
3. Search 2D Matrix
3. Search 2D Matrix
4. Koko Eating Bananas
4. Koko Eating Bananas
5. Median of Two Sorted Arrays
5. Median of Two Sorted Arrays
Search in Rotated Sorted Array
A sorted array that has been rotated at some pivot (e.g.,[4,5,6,7,0,1,2]) loses its global order but retains a useful property: at any midpoint, at least one half is still sorted. The trick is to determine which half is sorted, then check if the target falls within that sorted half. If it does, search there; otherwise, search the other half.
How to tell which half is sorted: If nums[left] <= nums[mid], the left half [left..mid] is sorted. Otherwise, the right half [mid..right] is sorted. The <= (not <) handles the case where left == mid (subarray of size 1 or 2).
Walked example: nums = [4,5,6,7,0,1,2], target = 0. left=0, right=6, mid=3 (value 7). Left half [4,5,6,7] is sorted, but 0 is not in [4,7], so search right. left=4, right=6, mid=5 (value 1). Right half [1,2] is sorted, 0 is not in [1,2], so search left. left=4, right=4, mid=4 (value 0). Found at index 4.
Edge case: Duplicates break this approach because nums[left] == nums[mid] no longer tells you which half is sorted. With duplicates, the worst case degrades to O(n). The standard workaround is to skip duplicates: if nums[left] == nums[mid]: left += 1; continue.
Time: O(log n) without duplicates. Space: O(1).
Template Code
Common Mistakes
Debugging Checklist
When your Binary Search solution fails:The Three Templates
Understanding which template to use is crucial:| Template | Loop Condition | Use Case | After Loop |
|---|---|---|---|
| Standard | left <= right | Find exact match | Return -1 if not found |
| Lower Bound | left < right | First >= target | left is answer |
| Upper Bound | left < right | First > target | left is answer |
Complexity Quick Reference
| Problem Type | Time | Space | Key Insight |
|---|---|---|---|
| Standard search | O(log n) | O(1) | Exact match |
| Lower/Upper bound | O(log n) | O(1) | First occurrence |
| Search on answer | O(n log range) | O(1) | Feasibility check |
| Rotated array | O(log n) | O(1) | Find sorted half |
| Peak finding | O(log n) | O(1) | Compare with neighbor |
Interview Problems by Company
- Easy
- Medium
- Hard
| Problem | Company | Key Concept |
|---|---|---|
| Binary Search | All | Basic template |
| Search Insert Position | Amazon, Google | Lower bound |
| First Bad Version | Meta, Microsoft | Boolean search |
| Sqrt(x) | Amazon | Search on answer |
| Valid Perfect Square | Search on answer |
Interview Tips
How to Explain Your Approach
How to Explain Your Approach
- “Since the array is sorted, I can use Binary Search for O(log n) time.”
- “I’ll maintain two pointers, left and right, representing the search space.”
- “At each step, I calculate mid and compare with target.”
- “Based on comparison, I eliminate half the search space.”
- “I repeat until I find the target or the space is exhausted.”
When Interviewer Says...
When Interviewer Says...
| Interviewer Says | You Should Think |
|---|---|
| ”Array is sorted” | Binary Search! |
| ”Can you do better than O(n)?” | Binary Search might work |
| ”Find minimum/maximum that satisfies…” | Binary Search on answer |
| ”Array is rotated” | Modified Binary Search |
| ”Find first/last occurrence” | Lower/Upper bound |
Common Follow-ups
Common Follow-ups
- “What if there are duplicates?” → Use lower/upper bound
- “What if array is rotated?” → Find sorted half first
- “Can you do it without extra space?” → Binary Search is already O(1) space
- “What about very large arrays?” → Binary Search scales well
Worked LeetCode Problems
These six problems span the full binary-search surface area: standard search, search in modified arrays, peak finding, and binary search on the answer. Drill these to instinct.LC 704 — Binary Search (Easy)
Brute force: linear scan. O(n). Optimized: standard binary search. O(log n) time, O(1) space.LC 33 — Search in Rotated Sorted Array (Medium)
Brute force: linear scan. O(n) — ignores the structure entirely. Optimized: at each step, determine which half is sorted, then check if target is inside that sorted half. O(log n).nums[left] <= nums[mid] and not strict <? When left == mid (subarray of size 1 or 2), the “left half” is a single element and is trivially sorted. Strict < would misclassify it.
LC 153 — Find Minimum in Rotated Sorted Array (Medium)
Brute force: linear scan. O(n). Optimized: binary search using the property that the minimum is the unique element smaller than its predecessor. O(log n).nums[right] and not nums[left]? Because nums[left] could be larger than nums[right] even after we have narrowed in. Comparing with nums[right] gives a clean signal: if mid > right, the rotation point (and min) lies strictly to the right. Otherwise, the min is at or before mid.
LC 162 — Find Peak Element (Medium)
Brute force: linear scan, return any index wherenums[i] > nums[i+1]. O(n).
Optimized: binary search. The “uphill” direction always contains a peak (because boundaries are -infinity). O(log n).
nums[-1] = nums[n] = -infinity, which guarantees a peak exists.
LC 410 — Split Array Largest Sum (Hard)
Brute force: try all ways to split the array into k subarrays. Exponential. Optimized: binary search on the answer. The answer space is[max(nums), sum(nums)], and the predicate “can we split into at most k subarrays each with sum at most X?” is monotonic in X.
left = max(nums)? Because no matter how we split, every subarray’s sum is at least the largest single element (we cannot break elements apart). Setting left lower makes can_split always return false for those values, wasting iterations.
Why right = sum(nums)? Because the largest possible answer is “put everything in one subarray,” which requires capacity equal to the total sum.
LC 875 — Koko Eating Bananas (Medium)
Brute force: try every speed from 1 to max(piles). O(max(piles) * n). Optimized: binary search on the answer. The predicate “can Koko finish at speed K within H hours?” is monotonic in K.right = max(piles) and not sum(piles)? Because at speed max(piles), Koko finishes the largest pile in 1 hour, and every smaller pile in 1 hour each, totaling at most n hours. Since the problem guarantees n <= h, this is always feasible. Using a tighter upper bound saves a few iterations.
Why ceiling division (p + k - 1) // k and not math.ceil(p / k)? Floating-point division introduces precision errors that break the comparison at edge values. Integer ceiling division is exact and faster.
Curated LeetCode List
A focused grind list grouped by difficulty. Solve all of these and you have covered the full binary-search surface area at FAANG.| # | Problem | Difficulty | Pattern | Why It Matters |
|---|---|---|---|---|
| 704 | Binary Search | Easy | Standard template | The warmup — master this in 60 seconds. |
| 35 | Search Insert Position | Easy | Lower bound | Canonical lower-bound application. |
| 278 | First Bad Version | Easy | Lower bound on boolean | Tests transition-point recognition. |
| 69 | Sqrt(x) | Easy | Binary search on answer | Gateway to the “search on answer” pattern. |
| 367 | Valid Perfect Square | Easy | Binary search on answer | Same skeleton as Sqrt. |
| 374 | Guess Number Higher or Lower | Easy | Standard with API call | Tests minimizing API calls. |
| 33 | Search in Rotated Sorted Array | Medium | Find sorted half | The classic rotated-array problem. |
| 81 | Search in Rotated Sorted Array II | Medium | Rotated with duplicates | O(n) worst case — know when. |
| 153 | Find Minimum in Rotated Sorted Array | Medium | Compare with right boundary | Cleaner than LC 33 — great warmup. |
| 154 | Find Minimum in Rotated II | Hard | Duplicates handling | Same as 153 but with right -= 1 on ties. |
| 162 | Find Peak Element | Medium | Walk uphill | Binary search WITHOUT a sorted array. |
| 34 | Find First and Last Position | Medium | Lower + upper bound | Tests both bound templates back to back. |
| 74 | Search a 2D Matrix | Medium | Flatten to 1D | Mind the row/col index math. |
| 240 | Search a 2D Matrix II | Medium | Staircase from top-right | NOT binary search — O(m+n). |
| 875 | Koko Eating Bananas | Medium | Binary search on answer | The canonical “search on answer” problem. |
| 1011 | Capacity to Ship Packages | Medium | Binary search on answer | Same skeleton as Koko. |
| 1482 | Minimum Days to Make Bouquets | Medium | Binary search on answer | Tests greedy feasibility check. |
| 1539 | Kth Missing Positive Number | Easy | Lower bound on transformed | Tests creative predicate design. |
| 4 | Median of Two Sorted Arrays | Hard | Binary search on partition | The hardest binary search problem. |
| 410 | Split Array Largest Sum | Hard | Binary search on answer | ”Minimize the maximum” — canonical pattern. |
| 668 | Kth Smallest in Multiplication Table | Hard | Binary search + count | Search-on-answer with non-trivial count. |
| 719 | Find Kth Smallest Pair Distance | Hard | Binary search + sliding window | Search on distance values. |
| 4 | Median of Two Sorted Arrays | Hard | Partition search | Truly hard; staff-level question. |
Interview Q&A — Senior-Level Walkthroughs
Q: Find the first and last position of an element in a sorted array (LC 34).
Q: Find the first and last position of an element in a sorted array (LC 34).
- Recognize that “first position” is lower bound and “last position” is upper bound minus 1.
- Run two separate binary searches. Both use Template 2 (
left < right). - After each search, validate that the returned index actually contains the target (otherwise the target is not present).
- State complexity: O(log n) per search, O(log n) total. O(1) space.
nums = [5,7,7,8,8,10], target = 8:- First search: looking for first index where
nums[i] >= 8. Converges to index 3. - Second search: looking for first index where
nums[i] > 8. Converges to index 5. Last = 5 - 1 = 4. - Result:
[3, 4].
last_occurrence(target) == lower_bound(target + 1) - 1. This works because the first index strictly greater than target is one past the last occurrence. Java’s Collections.binarySearch returns this directly via its return-value contract.upper_bound(target) - lower_bound(target). The two searches run in O(log n), and subtracting two indices is O(1). This is much better than finding any occurrence and then linearly scanning, which is O(n) when all elements are equal.- “Find any occurrence with standard binary search, then scan left and right linearly.” This is O(n) worst case (imagine an array of all equal values).
- Mixing template conventions — using
right = len(nums) - 1withright = mid, which can infinite-loop or skip the answer. - Not validating that
nums[first] == targetafter the first search. If target is absent,firstpoints to where it would be inserted, not to an actual occurrence.
- LC 34 (Find First and Last Position) — the source problem.
- LC 35 (Search Insert Position) — pure lower bound.
- LC 278 (First Bad Version) — lower bound on a boolean predicate.
Q: Koko Eating Bananas -- explain binary search on the answer (LC 875).
Q: Koko Eating Bananas -- explain binary search on the answer (LC 875).
- Identify that the answer space (eating speed K) is monotonic: if Koko finishes at speed K, she also finishes at speed K+1.
- Define the search bounds:
low = 1(smallest meaningful speed),high = max(piles)(any speed at or above this finishes in n hours). - Define the feasibility check: at speed K, total hours needed is the sum of
ceil(pile / K)for each pile. - Binary search using Template 2: when feasible, try smaller (
right = mid); else go bigger (left = mid + 1). - State complexity: O(n log m) where n = piles count, m = max pile size.
can_finish(k) for k = 1, 2, 3, … The output is a step function that goes from false, false, ..., false, true, true, ..., true. Binary search finds the transition point — the smallest k where the predicate flips to true.ceil(sum(piles) / k), a single calculation. The rest of the binary search structure stays the same. This actually makes the problem much easier — you can solve it analytically as k = ceil(sum(piles) / h).(p + k - 1) // k instead of math.ceil(p / k)?” Answer: floating-point division introduces precision errors. For very large p and small k, the result p / k may not be exactly representable as a double, and math.ceil could return one off the correct answer in edge cases. Integer ceiling is exact, branch-free, and faster.- “Try every speed from 1 to max(piles) and find the smallest that works.” This is O(max(piles) * n), which is way too slow when max(piles) is up to 10^9.
- Setting
left = 0— causes division by zero in the feasibility check. - Setting
right = sum(piles)— correct but loose, wastes iterations.max(piles)is the tight bound. - Using floating-point math in the feasibility check — precision bugs at edge cases.
- LC 875 (Koko Eating Bananas) — the gateway problem.
- LC 1011 (Capacity to Ship Packages) — structurally identical.
- LC 410 (Split Array Largest Sum) — the “minimize the maximum” version.
Q: Search in a rotated sorted array (LC 33). What if duplicates are allowed (LC 81)?
Q: Search in a rotated sorted array (LC 33). What if duplicates are allowed (LC 81)?
- Identify the key insight: after rotation, at least one half (
[left, mid]or[mid, right]) is always sorted. - Determine which half is sorted by comparing
nums[left]tonums[mid]. - Check if the target falls in the sorted half’s range. If yes, search there; else search the other half.
- For duplicates: when
nums[left] == nums[mid] == nums[right], you cannot tell which half is sorted — shrink by one. - State complexity: O(log n) without duplicates, O(n) worst case with duplicates.
<= in nums[left] <= nums[mid] matter?” Answer: when left == mid (subarray of size 1 or 2), the “left half” is a single element and is trivially sorted. Strict < would misclassify this case and send the search to the wrong half. The = ensures that single-element subarrays are correctly considered sorted.nums[left] == nums[mid] == nums[right], both halves look identical — you cannot eliminate either with certainty. The only safe move is to shrink by one, leading to linear worst case.nums[mid] with nums[right]. If nums[mid] > nums[right], the minimum is strictly to the right of mid (left = mid + 1). Else the minimum is at or to the left of mid (right = mid). This is cleaner than LC 33 because there is only one comparison branch.- Trying to find the rotation point first, then doing two separate binary searches. Two passes when one suffices, and harder to get right.
- Comparing
nums[mid]withnums[right]instead ofnums[left](mixing conventions). Both work but you must pick one and be consistent. - Not handling the duplicates case in LC 81 — the algorithm silently produces wrong results on
[1,1,3,1]searching for 3.
- LC 33 (Search in Rotated Sorted Array) — the source.
- LC 81 (Search in Rotated Sorted Array II) — the duplicates variant.
- LC 153 / 154 (Find Minimum in Rotated Sorted Array I/II) — companion problems.
Q: Median of Two Sorted Arrays in O(log(min(m,n))) (LC 4).
Q: Median of Two Sorted Arrays in O(log(min(m,n))) (LC 4).
- Recognize that the median divides all elements into two equal-sized halves — a “left half” and “right half” with the median straddling them.
- Binary search for the partition point in the smaller array. The partition in the larger array is determined by it (the two partitions together must equal half the total).
- The valid partition satisfies
max(left half) <= min(right half), specificallyA[i-1] <= B[j]andB[j-1] <= A[i]. - Handle edge cases with sentinel values (-infinity and +infinity) when the partition is at an array boundary.
- State complexity: O(log(min(m, n))) — always search the smaller array.
j = half - i must be in [0, n]. If we search the larger array, j could go negative for small i values, requiring extra bounds checking. Searching the smaller guarantees j stays valid.(total + 1) // 2 for half?” Answer: this works for both odd and even total length. For total = 5 (odd), half = 3, and the median is the maximum of the left halves. For total = 6 (even), half = 3, and the median is the average of max-of-lefts and min-of-rights. The +1 ensures the left half has the extra element when total is odd.half from (total + 1) // 2 to k. The same partition-search structure finds where exactly k elements are in the combined left halves. Complexity stays O(log(min(m, n))).- “Merge the two arrays and find the median.” O(m + n) — works but does not satisfy the O(log) requirement.
- Searching the larger array instead of the smaller — invalid
jindices for boundary cases. - Not using sentinel infinities for boundary partitions — causes index-out-of-bounds errors.
- LC 4 (Median of Two Sorted Arrays) — the source, rated Hard.
- “Median of Two Sorted Arrays” by Loïc Olive — the cleanest derivation I have seen.
- CLRS chapter 9 on selection algorithms.
Practice Problems
| Problem | Difficulty | Link |
|---|---|---|
| Binary Search | Easy | LeetCode 704 |
| Search Insert Position | Easy | LeetCode 35 |
| Search in Rotated Sorted Array | Medium | LeetCode 33 |
| Find Peak Element | Medium | LeetCode 162 |
| Median of Two Sorted Arrays | Hard | LeetCode 4 |
Practice Roadmap
Day 1: Standard Search
- Solve: Binary Search, Search Insert Position
- Focus: Master the basic template perfectly
Day 2: Bounds
- Solve: Find First and Last Position, First Bad Version
- Focus: Lower bound vs upper bound
Day 3: Modified Arrays
- Solve: Search in Rotated Array, Find Peak Element
- Focus: Finding the sorted half
Interview Questions
Q1: Why do we write mid = left + (right - left) / 2 instead of (left + right) / 2?
Q1: Why do we write mid = left + (right - left) / 2 instead of (left + right) / 2?
- The core issue is integer overflow. When
leftandrightare both large values close toInteger.MAX_VALUE(2^31 - 1 in Java/C++), adding them together exceeds the 32-bit signed integer range, wrapping around to a negative number.midthen becomes negative, causing an array-out-of-bounds crash or an infinite loop. left + (right - left) / 2avoids this becauseright - leftis always non-negative and smaller thanright, so the intermediate value never overflows.- In Python this is technically unnecessary because Python has arbitrary-precision integers. But you should still write it this way in interviews to show awareness, and because real systems often involve C/C++/Java/Rust where it matters.
- Real-world example: This was famously a bug in the Java standard library’s
Arrays.binarySearchfor nearly a decade (reported by Joshua Bloch in 2006). The JDK shipped with(left + right) / 2and it only manifested on arrays with over a billion elements. - Alternatively, some people write
(left + right) >>> 1(unsigned right shift) in Java, which also works because it treats the overflow bit correctly. Butleft + (right - left) / 2is clearer and portable. - Complexity: This is a constant-time operation either way; it does not change the O(log n) time or O(1) space complexity.
- “If Python handles big integers natively, can you name a language or environment where this bug would actually crash your program?” (Tests whether the candidate has written binary search outside of LeetCode, in C++/Java/Rust.)
- “What is the unsigned right shift operator
>>>in Java and why does it fix this problem?” (Tests bit-level understanding of two’s complement representation.)
Q2: Explain the difference between while (left <= right) and while (left < right). When do you use each?
Q2: Explain the difference between while (left <= right) and while (left < right). When do you use each?
while (left <= right)is for exact-match searches. The search space is[left, right](closed interval). Every iteration either finds the target and returns, or shrinks the interval by movingleft = mid + 1orright = mid - 1. Whenleft > right, the space is empty and the target does not exist.while (left < right)is for boundary/bound searches (finding the first element that satisfies a condition). The search space is[left, right)or you convergeleft == rightto a single answer. You typically doright = mid(notmid - 1) becausemiditself might be the answer.- The gotcha with
left < right: if you accidentally writeright = mid - 1, you may skip the answer. If you accidentally writeleft = mid(without+ 1), you get an infinite loop whenleft + 1 == rightbecausemidequalsleftand nothing changes. - My mental model:
left <= rightmeans “I am still searching and have not found it yet.”left < rightmeans “I am narrowing down to a single candidate.” - Time/Space: Both are O(log n) time, O(1) space. The difference is purely in correctness, not performance.
left <= right” or inability to explain what happens when you mix the wrong loop condition with the wrong pointer update. This signals the candidate will produce subtle bugs on boundary problems.Follow-ups:- “Write a binary search that finds the leftmost occurrence of a target in a sorted array with duplicates. Which template do you use and why?” (Tests whether they can convert the conceptual understanding into code on the spot.)
- “If I use
while (left < right)but writeleft = midinstead ofleft = mid + 1, what exactly happens? Walk me through a concrete two-element array.” (Tests ability to trace through code and identify infinite loops.)
Q3: Search in a rotated sorted array. Walk me through your approach.
Q3: Search in a rotated sorted array. Walk me through your approach.
- Key insight: Even though the array is rotated, at least one half (left or right of
mid) is always sorted. We can determine which half is sorted by comparingnums[left]withnums[mid]. - Algorithm:
- Compute
mid. Ifnums[mid] == target, return. - If
nums[left] <= nums[mid], the left half is sorted. Check iftargetfalls in[nums[left], nums[mid]). If yes, search left; otherwise search right. - Else the right half is sorted. Check if
targetfalls in(nums[mid], nums[right]]. If yes, search right; otherwise search left.
- Compute
- Why
<=and not<in the check? Whenleft == mid(two-element subarray), the left “half” is just one element and is trivially sorted. Using<would incorrectly classify it and send you to the wrong half. - Edge cases to mention: Array of size 1, array not rotated at all (rotation by 0), target not present, all elements equal.
- Complexity: O(log n) time, O(1) space. However, if duplicates are allowed (LeetCode 81 variant), worst case degrades to O(n) because you cannot determine which half is sorted when
nums[left] == nums[mid] == nums[right].
- “What changes if the rotated array contains duplicates? What is the new worst-case time complexity and why?” (Tests awareness of the O(n) degenerate case and how to handle
nums[left] == nums[mid].) - “Could you find the rotation point (minimum element) using binary search? How does that relate to this problem?” (Tests whether the candidate sees the connection between finding the pivot and the search problem.)
Q4: What is 'binary search on the answer' and when do you use it?
Q4: What is 'binary search on the answer' and when do you use it?
- The core idea: Instead of searching for a target in an array, you binary search over the space of possible answers. For each candidate answer, you run a feasibility check (a greedy O(n) function) to determine if that answer works. Since the feasibility function is monotonic (if capacity X works, then X+1 also works), binary search applies.
- Template: Define
low= minimum possible answer,high= maximum possible answer. Binary search: ifis_feasible(mid), try smaller (high = mid); else go bigger (low = mid + 1). Returnlow. - When to use it:
- “What is the minimum capacity/speed/value such that [condition]?”
- “What is the maximum X such that [condition]?”
- Any problem where brute-forcing all possible answers is too slow but checking one answer is fast.
- Classic examples: Koko Eating Bananas (minimum speed), Capacity to Ship Packages (minimum capacity), Split Array Largest Sum (minimize the maximum subarray sum), Aggressive Cows (maximize minimum distance).
- The hardest part is defining the search space bounds correctly. For “minimum capacity to ship,”
low = max(single_weight)(you must be able to carry the heaviest item) andhigh = sum(all_weights)(ship everything in one day). - Complexity: O(n log R) where R is the range of the answer space and n is the cost of the feasibility check. Space is O(1).
low = 0 instead of low = max(weights) in the shipping problem, which would make the feasibility check fail for impossible capacities).Follow-ups:- “In the ‘Split Array Largest Sum’ problem, you need to minimize the maximum subarray sum when splitting into k parts. Walk me through how you would set up the binary search bounds and the feasibility check.” (Tests whether the candidate can apply the pattern to a new problem in real time.)
- “What happens if the feasibility function is not monotonic? Can you still use binary search on the answer? Give an example.” (Tests deep understanding of the monotonicity requirement. Answer: No, binary search requires that if
f(x)is true, thenf(x+1)is also true for the minimization direction.)
Q5: Find the median of two sorted arrays in O(log(min(m,n))) time.
Q5: Find the median of two sorted arrays in O(log(min(m,n))) time.
- Key insight: The median divides all elements into two equal halves. Instead of merging the arrays (O(m+n)), we binary search for the correct partition point in the smaller array and derive the other partition from it.
- Setup: Let A be the smaller array (length m), B the larger (length n). We binary search
iin[0, m]representing how many elements from A go in the left half. Thenj = (m + n + 1) / 2 - ielements from B go in the left half. - Invariant: For a valid partition,
A[i-1] <= B[j]andB[j-1] <= A[i]. IfA[i-1] > B[j],iis too big (move left). IfB[j-1] > A[i],iis too small (move right). - Edge cases: When
i = 0ori = m, there are no elements on one side from A, so use negative/positive infinity for comparisons. Same forj = 0orj = n. - Result: If total length is odd, median is
max(A[i-1], B[j-1]). If even, median is(max(A[i-1], B[j-1]) + min(A[i], B[j])) / 2. - Complexity: O(log(min(m,n))) time, O(1) space. We always binary search the smaller array to minimize iterations.
(m + n + 1) / 2 handles both even and odd cases.Follow-ups:- “Why do we specifically binary search the smaller array and not the larger one? What changes if we searched the larger array?” (Tests understanding that searching the smaller array guarantees
jstays non-negative, avoiding invalid indices.) - “How would you extend this approach to find the kth smallest element across two sorted arrays instead of the median?” (Tests generalization ability. Answer: adjust the partition sizes so the left half contains exactly k elements.)
Q6: Find a peak element in an unsorted array in O(log n) time.
Q6: Find a peak element in an unsorted array in O(log n) time.
- A peak element is one that is greater than its neighbors. The problem guarantees
nums[-1] = nums[n] = -infinity(boundary elements are smaller than everything), which guarantees at least one peak exists. - Why binary search works here: Compare
nums[mid]withnums[mid + 1]. Ifnums[mid] < nums[mid + 1], the right side is increasing, so a peak must exist to the right (eithermid + 1is the peak or values eventually decrease, creating a peak). Moveleft = mid + 1. Otherwise, the left side (includingmid) contains a peak. Moveright = mid. - The key insight is the “mountain guarantee”: because boundaries are
-infinity, any increasing direction must eventually come back down, guaranteeing a peak. This is why we can safely discard half the array. - Template: Use
while (left < right)withright = mid(notmid - 1), becausemiditself could be the peak. Whenleft == right, that index is the answer. - Complexity: O(log n) time, O(1) space. We do not find the highest peak, just any peak, which is sufficient.
left <= right with right = mid - 1, which can skip the peak.Follow-ups:- “What if I asked you to find a peak in a 2D matrix where a peak is greater than all four neighbors? Can you do better than O(m*n)?” (Tests ability to extend the concept. Answer: O(m log n) or O(n log m) by applying peak finding on rows/columns iteratively.)
- “Does this algorithm always find the global maximum? Why or why not?” (Answer: No. It finds a local peak. The global maximum would require O(n) in the worst case since the array is unsorted.)
Q7: Given a sorted array with duplicates, find the first and last position of a target. What is your approach?
Q7: Given a sorted array with duplicates, find the first and last position of a target. What is your approach?
- Run two separate binary searches: one to find the leftmost occurrence (lower bound) and one to find the rightmost occurrence.
- For the leftmost (lower bound): Use
while (left < right). Whenarr[mid] >= target, setright = mid(keep searching left,midmight be the first). Whenarr[mid] < target, setleft = mid + 1. After the loop, check ifarr[left] == target. - For the rightmost (upper bound): Use
while (left < right). Whenarr[mid] <= target, setleft = mid + 1(keep searching right). Whenarr[mid] > target, setright = mid. After the loop,left - 1is the last position. Alternatively, you can usemid = left + (right - left + 1) / 2(ceiling division) withleft = midto avoid infinite loops. - Alternatively, for the rightmost, find the upper bound (first element > target) and subtract 1. This is often cleaner:
upper_bound(target) - 1. - Edge cases: target not in the array at all (return
[-1, -1]), all elements are the target, target is at the boundaries. - Complexity: O(log n) time for each search, so O(log n) total. O(1) space. Much better than finding target and then linearly expanding outward, which is O(n) in the worst case when all elements are equal.
- “Can you express ‘find first occurrence’ and ‘find last occurrence’ both using a single lower_bound function with different arguments?” (Tests deeper abstraction: last occurrence of
xequalslower_bound(x+1) - 1.) - “How would you count the total number of occurrences of target in O(log n)?” (Answer:
upper_bound(target) - lower_bound(target). Tests whether they can compose the primitives.)
Q8: You are given a function isBadVersion(n) that tells you if version n is bad. Find the first bad version among 1 to n.
Q8: You are given a function isBadVersion(n) that tells you if version n is bad. Find the first bad version among 1 to n.
false to true, and you need the boundary. The interviewer also wants to see if you handle the API call count concern (minimizing calls to isBadVersion, which might be expensive).Strong Answer:- This is a textbook lower-bound binary search on a boolean predicate. The “array” is conceptually
[false, false, ..., false, true, true, ..., true]. We need the firsttrue. - Use
while (left < right)withright = mid(notmid - 1) becausemidcould be the first bad version. IfisBadVersion(mid)is true, the answer ismidor earlier. If false, the answer is aftermid, soleft = mid + 1. - Why
left <= rightis wrong here: It would require a separate variable to track the answer and extra logic, making the code messier. Theleft < righttemplate naturally converges to the exact transition point. - API call count: Exactly
ceil(log2(n))calls. For n = 1 billion, that is about 30 calls. IfisBadVersionis a network call (e.g., checking a remote CI system), this efficiency matters. - Complexity: O(log n) time, O(1) space.
left <= right with right = mid - 1 and not tracking the answer separately, which would skip the first bad version.Follow-ups:- “What if
isBadVersionis flaky and sometimes returns incorrect results? How would you make your search robust?” (Tests real-world thinking. Answer: call it multiple times per version and take majority vote, or use a probabilistic approach, accepting O(log n * k) calls for k repetitions.) - “What if instead of a linear version history, versions form a DAG (like git commits with branches and merges)? How would you find the first bad commit?” (Tests ability to generalize beyond arrays. This is essentially
git bisect, which handles DAGs by linearizing the commit history.)
Q9: How would you apply binary search to solve Koko Eating Bananas?
Q9: How would you apply binary search to solve Koko Eating Bananas?
- Problem recap: Koko has
npiles of bananas. She eats at speedkbananas/hour (one pile at a time, rounds up partial hours). Find the minimumkto finish all piles inhhours. - Why binary search applies: The answer space
kis monotonic. If Koko can finish at speedk, she can definitely finish at speedk+1. So we binary search for the smallestkwherecan_finish(k, h)is true. - Search bounds:
low = 1(minimum meaningful speed),high = max(piles)(eating the largest pile in one hour means she finishes everything in n hours, and n <= h is guaranteed by constraints). - Feasibility check
can_finish(k, h): For each pilep, the hours needed areceil(p / k). Sum all hours. If total <= h, return true. Theceilcan be computed as(p + k - 1) / kusing integer math to avoid floating-point issues. - Template:
while (low < high), ifcan_finish(mid)thenhigh = mid, elselow = mid + 1. Returnlow. - Complexity: O(n log m) where n = number of piles, m = max pile size. The feasibility check is O(n), and we run it O(log m) times. Space is O(1).
high = sum(piles) instead of max(piles) (technically still correct but shows lack of tight bound reasoning). Or using floating-point division in the feasibility check (introduces precision bugs).Follow-ups:- “What if Koko can eat from multiple piles in the same hour (no restriction of one pile per hour)? How does the feasibility check change?” (Tests ability to modify the greedy check. Answer: total bananas / k gives exact hours needed since no per-pile rounding.)
- “Can you generalize this pattern? Give me two other problems that use the exact same structure.” (Tests pattern recognition. Good answers: Capacity to Ship Packages within D Days, Minimum Number of Days to Make m Bouquets, Magnetic Force Between Two Balls.)
Q10: You have a sorted matrix where each row and column is sorted. Find a target. What is the optimal approach?
Q10: You have a sorted matrix where each row and column is sorted. Find a target. What is the optimal approach?
- Clarify which type of sorted matrix: There are two common variants.
- Variant 1 (LeetCode 74): Each row is sorted, and the first element of each row is greater than the last element of the previous row. The entire matrix is one sorted sequence.
- Variant 2 (LeetCode 240): Each row is sorted left-to-right and each column is sorted top-to-bottom, but rows are NOT chained. This is the more interesting problem.
- For Variant 1: Treat it as a 1D sorted array of size
m * n. Binary search with index mapping:row = mid / n,col = mid % n. Time: O(log(m*n)), Space: O(1). - For Variant 2 (the staircase search): Start from the top-right corner. If the current value equals target, return true. If it is greater than target, move left (eliminate that column). If it is less, move down (eliminate that row). Each step eliminates a row or column.
- Why top-right (or bottom-left)? These corners give you a decision point: one direction increases values, the other decreases. Top-left and bottom-right do not have this property (both directions increase or both decrease).
- Complexity: Variant 1: O(log(m*n)) time. Variant 2: O(m + n) time. Both use O(1) space.
- Can you beat O(m+n) for Variant 2? Yes, with binary search on each row: O(m log n). Or with a more advanced approach: binary search on the diagonal and then recurse on submatrices for O(m log(2n/m)) when m < n. But the staircase approach is almost always sufficient and much simpler.
- “If I only need to check existence, the staircase approach is O(m+n). But what if I need to count all elements less than a target? Can the staircase approach help?” (Tests creative adaptation. Answer: Yes, walk the staircase and sum up column counts as you go, still O(m+n).)
- “What if the matrix is very tall (m >> n)? Which approach becomes better and why?” (Tests complexity analysis. When m >> n, binary search per row O(m log n) might lose to staircase O(m + n) since n is small. But if m >> n and you can search columns, O(n log m) beats both.)
Interview Deep-Dive
You are given a problem that says 'find the minimum X such that condition C holds.' How do you recognize this as binary search on the answer, and what are the three things you must define correctly?
You are given a problem that says 'find the minimum X such that condition C holds.' How do you recognize this as binary search on the answer, and what are the three things you must define correctly?
- Recognition signal: The answer space is ordered (integers or real numbers), and the feasibility function is monotonic — if X works, then X+1 also works (for minimization). This means there is a clean boundary between “does not work” and “works,” and binary search finds that boundary.
- The three things you must define:
- Search bounds (low and high). Low = the smallest possible answer. High = the largest possible answer. Getting these wrong means you either miss the answer or search an unnecessarily large range. For “minimum speed to eat bananas in H hours”: low = 1, high = max(piles). For “minimum capacity to ship packages in D days”: low = max(weights), high = sum(weights).
- Feasibility function is_feasible(mid). This is typically a greedy O(n) function that checks whether the candidate answer
midsatisfies the constraint. For bananas: sum of ceil(pile/mid) for each pile, check if total hours is within H. For shipping: greedily pack items into days, check if days needed is within D. - Loop template and convergence. Use
while low < high, withhigh = midwhen feasible (try smaller) andlow = mid + 1when not. Returnlow. This converges to the smallest feasible value.
- Common mistake: Setting
lowtoo low (e.g., low = 0 when the minimum meaningful answer is 1) causes the feasibility function to receive invalid inputs (division by zero for speed problems). - Complexity: O(n * log(range)) where n is the cost of the feasibility check and range = high - low. For integer answers, log(range) is typically 30-40 iterations.
- No. Binary search requires that the predicate transitions from false to true (or vice versa) exactly once. If there are multiple transitions, binary search will converge to one transition and miss the others.
- If the function is non-monotonic, you need either linear search, ternary search (for unimodal functions), or a completely different approach.
What are the three binary search templates, and what is the exact off-by-one error that occurs when you mix them? Trace through a concrete two-element array.
What are the three binary search templates, and what is the exact off-by-one error that occurs when you mix them? Trace through a concrete two-element array.
- Template 1 (exact match):
while left <= right, updatesleft = mid + 1andright = mid - 1. Search space is closed interval[left, right]. Returns inside the loop when found, or -1 after the loop. - Template 2 (lower bound / first true):
while left < right, updatesleft = mid + 1andright = mid. Search space converges to a single point.miditself might be the answer, so we keep it in the range withright = mid. - Template 3 (upper bound / last true):
while left < right, updatesleft = midandright = mid - 1. Requires ceiling division for mid:mid = left + (right - left + 1) / 2to avoid infinite loops. - The mixing error — concrete trace with
[3, 5], target = 3, using Template 1 conditions with Template 2 updates:- left=0, right=1 (should be right=len=2 for Template 2, but we used right=len-1=1).
- mid = 0. arr[0]=3 >= target, so
right = mid = 0. - left=0, right=0. Loop condition
left < rightis false. Return left=0. Correct by luck. - Now target = 5: left=0, right=1. mid=0.
arr[0]=3 < 5, soleft = mid + 1 = 1. left=1, right=1. Loop ends. Return 1. Correct. - But if right was initialized to
len-1instead oflenand target = 6: the loop exits at left=right=1, returning 1 instead of 2 (the correct insertion point). This is the off-by-one.
- The rule: Template 2’s right must be initialized to
len(one past the end) because the answer might be “past all elements.” Template 1’s right islen - 1because it searches within existing elements.
- Ask: “Am I looking for an exact value, or a boundary?” Exact value = Template 1. First element satisfying a condition = Template 2. Last element satisfying a condition = Template 3 (or Template 2 on the complement).
- If unsure, default to Template 2. It handles the most common interview scenarios and naturally converges without the
right = mid - 1trap.
Binary search in a rotated sorted array -- what is the single most common bug, and how does the presence of duplicates change the worst-case complexity from O(log n) to O(n)?
Binary search in a rotated sorted array -- what is the single most common bug, and how does the presence of duplicates change the worst-case complexity from O(log n) to O(n)?
- The most common bug: Using
nums[left] < nums[mid]instead ofnums[left] <= nums[mid]when determining which half is sorted. The=matters for two-element subarrays whereleft == mid. Without it, a two-element subarray[3, 1]with mid=0 would classify the left half as unsorted, sending the search to the wrong half. - How duplicates break O(log n): When
nums[left] == nums[mid] == nums[right], you cannot determine which half is sorted. Consider[1, 1, 1, 1, 1, 1, 1]rotated to[1, 1, 3, 1, 1, 1, 1]. With mid pointing to a1, both halves look identical. You cannot eliminate either half with certainty. - The only safe move with duplicates: Shrink the search space by one element:
left += 1(orright -= 1). This degrades the worst case to O(n) when all elements are equal except one. - Why this is unavoidable: Information-theoretically, if n-1 elements are the same and one is different, any comparison-based algorithm needs O(n) comparisons in the worst case to locate the different element.
- Interview tip: Always ask “can the array contain duplicates?” before coding. If yes, mention the O(n) worst case explicitly.
- In the worst case (all elements equal except one), yes, O(n) is optimal. No algorithm can do better.
- In the average case with random data, binary search still achieves O(log n) expected time because the duplicate collision is rare.
- A practical adaptive approach: when you encounter
nums[left] == nums[mid], increment left by 1 and check if the next element differs. If it does, resume binary search immediately. This gives O(log n) when duplicates are sparse, degrading gracefully to O(n) only when duplicates dominate.