Binary Search
Phase 2 14 solutionsBinary Search
mindmap
root((Binary Search))
Classic Search
Binary Search LC704
Search Insert Position LC35
Guess Number Higher Or Lower LC374
Search a 2D Matrix LC74
Search on Answer
Sqrt x LC69
Koko Eating Bananas LC875
Capacity to Ship Packages LC1011
Split Array Largest Sum LC410
Rotated Array
Find Minimum In Rotated Sorted Array LC153
Search In Rotated Sorted Array LC33
Search In Rotated Sorted Array II LC81
Design
Time Based Key Value Store LC981
Hard
Median of Two Sorted Arrays LC4
Find in Mountain Array LC1095
Problem List
| # | Problem | Difficulty | Technique |
|---|---|---|---|
| 1 | Binary Search (LC 704) | Easy | Classic |
| 2 | Search Insert Position (LC 35) | Easy | Lower Bound |
| 3 | Guess Number Higher Or Lower (LC 374) | Easy | Classic |
| 4 | Sqrt(x) (LC 69) | Easy | Answer Space |
| 5 | Search a 2D Matrix (LC 74) | Medium | Flatten Index |
| 6 | Koko Eating Bananas (LC 875) | Medium | Answer Space |
| 7 | Capacity to Ship Packages (LC 1011) | Medium | Answer Space |
| 8 | Find Minimum In Rotated Sorted Array (LC 153) | Medium | Rotated Array |
| 9 | Search In Rotated Sorted Array (LC 33) | Medium | Rotated Array |
| 10 | Search In Rotated Sorted Array II (LC 81) | Medium | Rotated + Duplicates |
| 11 | Time Based Key Value Store (LC 981) | Medium | Lower Bound |
| 12 | Split Array Largest Sum (LC 410) | Hard | Answer Space |
| 13 | Median of Two Sorted Arrays (LC 4) | Hard | Partition |
| 14 | Find in Mountain Array (LC 1095) | Hard | Peak + Two BS |
Mermaid Diagrams
1. Binary Search Template
flowchart TD
A([l=0, r=n-1]) --> B{"l <= r"}
B -->|Yes| C["mid = l + r // 2"]
C --> D{arr-mid == target?}
D -->|Yes| E[Return mid]
D -->|arr-mid < target| F[l = mid + 1]
D -->|arr-mid > target| G[r = mid - 1]
F --> B
G --> B
B -->|No| H["Return -1 or l<br/>depends on variant"]
2. Search Space Categories
flowchart TD
A[Binary Search Problem] --> B{What are we searching?}
B -->|Index in sorted array| C["Array Index Space<br/>l=0, r=n-1"]
B -->|Optimal value / answer| D["Answer Space<br/>l=min_possible, r=max_possible"]
C --> C1["LC 704, LC 35, LC 74<br/>LC 33, LC 81, LC 153"]
D --> D1["LC 69, LC 875, LC 1011<br/>LC 410"]
D --> D2["Key: define feasibility<br/>function f-x is monotone"]
C --> C2["Key: sorted array<br/>or rotated sorted array"]
3. Rotated Array Binary Search Logic (LC 33 / LC 153)
flowchart TD
A[mid computed] --> B{"arr-mid >= arr-l?<br/>Left half sorted?"}
B -->|Yes - left sorted| C{"target in left half?<br/>target >= arr-l AND target < arr-mid"}
C -->|Yes| D[r = mid - 1]
C -->|No| E[l = mid + 1]
B -->|No - right half sorted| F{"target in right half?<br/>target > arr-mid AND target <= arr-r"}
F -->|Yes| E
F -->|No| D
4. Binary Search on Answer Pattern
flowchart TD
A["Identify answer range<br/>l=lo, r=hi"] --> B["mid = l + r // 2"]
B --> C{feasible-mid ?}
C -->|Yes - try smaller/larger| D{Minimize or Maximize?}
D -->|Minimize answer| E["ans=mid<br/>r=mid-1"]
D -->|Maximize answer| F["ans=mid<br/>l=mid+1"]
C -->|No| G{Minimize or Maximize?}
G -->|Minimize| H[l=mid+1]
G -->|Maximize| I[r=mid-1]
E --> J{"l <= r?"}
F --> J
H --> J
I --> J
J -->|Yes| B
J -->|No| K[Return ans]
Complexity Summary
| Problem | Time | Space | Key Insight |
|---|---|---|---|
| LC 704 | O(log n) | O(1) | Classic template |
| LC 35 | O(log n) | O(1) | Return l when not found |
| LC 374 | O(log n) | O(1) | Use API as comparator |
| LC 69 | O(log x) | O(1) | Answer space [0, x] |
| LC 74 | O(log mn) | O(1) | Treat as 1D array |
| LC 875 | O(n log m) | O(1) | Feasibility: hours <= h |
| LC 1011 | O(n log s) | O(1) | Feasibility: days <= d |
| LC 153 | O(log n) | O(1) | Find inflection point |
| LC 33 | O(log n) | O(1) | Identify sorted half |
| LC 81 | O(log n) avg | O(1) | Skip duplicates: l++ |
| LC 981 | O(log n) per query | O(n) | Lower bound on timestamps |
| LC 410 | O(n log s) | O(1) | Feasibility: splits <= m |
| LC 4 | O(log min(m,n)) | O(1) | Partition smaller array |
| LC 1095 | O(log n) | O(1) | Find peak, BS each side |
Study Order
flowchart LR
subgraph S1["Stage 1: Classic Binary Search"]
bs1["binary_search(704) → search_insert_position(35)"]
bs2["guess_number(374) → sqrtx(69)"]
end
subgraph S2["Stage 2: Modified Arrays"]
bs3["search_2d_matrix(74) → find_minimum_rotated(153)"]
bs4["search_rotated(33) → search_rotated_ii(81)"]
end
subgraph S3["Stage 3: Binary Search on Answer"]
bs5["koko_eating_bananas(875) → capacity_to_ship(1011)"]
bs6["split_array_largest_sum(410)"]
end
subgraph S4["Stage 4: Hard"]
bs7["time_based_key_value(981) → find_in_mountain_array(1095)"]
bs8["median_two_arrays(4)"]
end
S1 --> S2 --> S3 --> S4
Recommended order: Classic → Lower bound → Answer space → Rotated → Hard
Binary Search — Pattern Notes
Core Intuition
Binary search works on any monotonic search space. If you can define a predicate f(x) where all values on one side are True and the other False, you can binary search it. This extends to searching on the answer itself rather than an index.
Sub-patterns:
- Classic BS — find exact value or insertion point in sorted array
- BS on Answer — search space is the range of possible answers; check feasibility with a helper
- Rotated Arrays — one half is always sorted; use that to decide which half to search
- 2D / Implicit — flatten or use row/col properties
Decision Diagram
flowchart TD
A[Monotonic predicate exists?] -->|Yes| B{Search space?}
A -->|No| Z[Not a BS problem]
B --> C[Index in sorted array]
B --> D[Range of possible answer values]
B --> E[Rotated sorted array]
B --> F[2D matrix]
C --> C1["Classic lo/hi with target comparison"]
D --> D1["lo=min feasible, hi=max feasible<br/>check feasibility with greedy/simulation"]
E --> E1["Identify sorted half first<br/>then decide which half has target"]
F --> F1["Treat as 1D if row-major sorted<br/>mid maps to matrix-mid//cols-mid%cols"]
Per-Problem Notes
| # | Problem | LC | Difficulty | What to Learn | Common Mistakes | Time / Space |
|---|---|---|---|---|---|---|
| 1 | Binary Search | 704 | Easy | Template: lo+(hi-lo)//2; loop while lo<=hi |
lo+hi overflow; wrong loop condition |
O(log n) / O(1) |
| 2 | Search Insert Position | 35 | Easy | Return lo at end — that's the insertion point |
Returning mid when element absent |
O(log n) / O(1) |
| 3 | Guess Number | 374 | Easy | API-based BS; guess(mid) returns -1/0/1 |
Confusing return value semantics | O(log n) / O(1) |
| 4 | Sqrt(x) | 69 | Easy | BS on [0,x]; find largest mid where mid*mid<=x; return hi |
Overflow: use mid <= x//mid |
O(log x) / O(1) |
| 5 | Search 2D Matrix | 74 | Medium | Flatten: mid → matrix[mid//cols][mid%cols] |
Using staircase search (not needed here) | O(log mn) / O(1) |
| 6 | Koko Bananas | 875 | Medium | BS on answer [1, max(piles)]; feasibility: sum(ceil(p/k)) <= h |
p//k instead of (p+k-1)//k |
O(n log M) / O(1) |
| 7 | Ship Packages | 1011 | Medium | BS on answer; lo=max(weights), hi=sum(weights); greedy feasibility |
Setting lo=1 instead of max(weights) |
O(n log S) / O(1) |
| 8 | Find Min Rotated | 153 | Medium | If nums[mid] > nums[hi] → min in right half; else left (incl. mid) |
Comparing with nums[lo] instead of nums[hi] |
O(log n) / O(1) |
| 9 | Search Rotated | 33 | Medium | Identify sorted half first, then check if target in that half | Skipping the "which half is sorted" check | O(log n) / O(1) |
| 10 | Search Rotated II | 81 | Medium | Same as 33 but when nums[lo]==nums[mid]==nums[hi] → lo++, hi-- |
Not handling equal case → infinite loop | O(log n) avg, O(n) worst / O(1) |
| 11 | Time Map | 981 | Medium | Store (timestamp, value) list per key; bisect_right then subtract 1 for floor |
Off-by-one with bisect_right | O(log n) get, O(1) set / O(n) |
| 12 | Split Array | 410 | Hard | BS on answer; lo=max(nums), hi=sum(nums); greedy count of pieces |
Feasibility off-by-one: start pieces=1 | O(n log S) / O(1) |
| 13 | Median Two Arrays | 4 | Hard | BS on partition of smaller array; ensure left halves total (m+n+1)//2 |
Not BS-ing on smaller array; odd/even handling | O(log min(m,n)) / O(1) |
| 14 | Mountain Array | 1095 | Hard | Three-phase: find peak, BS ascending left, BS descending right | Not searching right side; forgetting right is descending | O(log n) / O(1) |
Edge Cases to Watch
- Single-element array — loop runs once, works correctly
- All duplicates (LC 81) — must handle
lo++, hi--shrink - Target smaller/larger than all elements → insertion at 0 or n
x=0for Sqrt → return 0- Rotated array that is not actually rotated (pivot at index 0)
- Median with very different sized arrays — always BS on smaller
- Koko/Ship:
h >= nalways feasible athi;h=1means one trip
| # | Problem | Difficulty | Technique |
|---|---|---|---|
| 1 | Binary Search (LC 704) | Easy | Classic |
| 2 | Search Insert Position (LC 35) | Easy | Lower Bound |
| 3 | Guess Number Higher Or Lower (LC 374) | Easy | Classic |
| 4 | Sqrt(x) (LC 69) | Easy | Answer Space |
| 5 | Search a 2D Matrix (LC 74) | Medium | Flatten Index |
| 6 | Koko Eating Bananas (LC 875) | Medium | Answer Space |
| 7 | Capacity to Ship Packages (LC 1011) | Medium | Answer Space |
| 8 | Find Minimum In Rotated Sorted Array (LC 153) | Medium | Rotated Array |
| 9 | Search In Rotated Sorted Array (LC 33) | Medium | Rotated Array |
| 10 | Search In Rotated Sorted Array II (LC 81) | Medium | Rotated + Duplicates |
| 11 | Time Based Key Value Store (LC 981) | Medium | Lower Bound |
| 12 | Split Array Largest Sum (LC 410) | Hard | Answer Space |
| 13 | Median of Two Sorted Arrays (LC 4) | Hard | Partition |
| 14 | Find in Mountain Array (LC 1095) | Hard | Peak + Two BS |
Binary Search ▼ expand
"""
LC 704 - Binary Search
======================
Given an array of integers `nums` which is sorted in ascending order, and an
integer `target`, write a function to search `target` in `nums`. If `target`
exists, return its index. Otherwise, return -1.
Examples
--------
```text
Input: nums = [-1, 0, 3, 5, 9, 12], target = 9
Output: 4
Input: nums = [-1, 0, 3, 5, 9, 12], target = 2
Output: -1
```
Constraints
-----------
- 1 <= nums.length <= 10^4
- -10^4 < nums[i], target < 10^4
- All integers in nums are unique
- nums is sorted in ascending order
"""
from typing import List
class Solution:
def method_brute_force(self, nums: List[int], target: int) -> int:
# O(n) time | O(1) space
for i, v in enumerate(nums):
if v == target:
return i
return -1
def search(self, nums: List[int], target: int) -> int:
# Standard binary search: compare mid to target, halve search space
# O(log n) time | O(1) space
lo, hi = 0, len(nums) - 1
while lo <= hi:
mid = (lo + hi) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
lo = mid + 1
else:
hi = mid - 1
return -1
if __name__ == "__main__":
s = Solution()
assert s.search([-1, 0, 3, 5, 9, 12], 9) == 4
assert s.search([-1, 0, 3, 5, 9, 12], 2) == -1
assert s.method_brute_force([-1, 0, 3, 5, 9, 12], 9) == 4
assert s.method_brute_force([-1, 0, 3, 5, 9, 12], 2) == -1
print("All tests passed.") Capacity To Ship Packages ▼ expand
"""
LC 1011 - Capacity To Ship Packages Within D Days
===================================================
A conveyor belt has packages that must be shipped from one port to another
within `days` days. The i-th package on the conveyor belt has a weight of
`weights[i]`. Each day, we load the ship with packages in the order given by
`weights`. We may not load more weight than the maximum weight capacity of the
ship.
Return the least weight capacity of the ship that will result in all the
packages on the conveyor belt being shipped within `days` days.
Examples
--------
```text
Input: weights = [1,2,3,4,5,6,7,8,9,10], days = 5
Output: 15
Input: weights = [3,2,2,4,1,4], days = 3
Output: 6
Input: weights = [1,2,3,1,1], days = 4
Output: 3
```
Constraints
-----------
- 1 <= days <= weights.length <= 5 * 10^4
- 1 <= weights[i] <= 500
"""
from typing import List
class Solution:
def _days_needed(self, weights: List[int], cap: int) -> int:
# Simulate loading: start new day when adding next package exceeds capacity
days, load = 1, 0
for w in weights:
if load + w > cap:
days += 1
load = 0
load += w
return days
def method_linear(self, weights: List[int], days: int) -> int:
# Try every capacity from max(weights) upward — O(sum*n)
# O(sum(weights) * n) time | O(1) space
cap = max(weights)
while self._days_needed(weights, cap) > days:
cap += 1
return cap
def shipWithinDays(self, weights: List[int], days: int) -> int:
# Binary search on capacity: lo=max(weights) (must fit heaviest), hi=sum(weights)
# If days_needed(mid) <= days, mid is feasible — try smaller capacity
# O(n * log(sum(weights))) time | O(1) space
lo, hi = max(weights), sum(weights)
while lo < hi:
mid = (lo + hi) // 2
if self._days_needed(weights, mid) <= days:
hi = mid
else:
lo = mid + 1
return lo
if __name__ == "__main__":
s = Solution()
assert s.shipWithinDays([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5) == 15
assert s.shipWithinDays([3, 2, 2, 4, 1, 4], 3) == 6
assert s.shipWithinDays([1, 2, 3, 1, 1], 4) == 3
assert s.method_linear([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5) == 15
print("All tests passed.") Find In Mountain Array ▼ expand
"""
LC 1095 - Find in Mountain Array
==================================
You may recall that an array `arr` is a mountain array if and only if:
- arr.length >= 3
- There exists some index i (0-indexed) with 0 < i < arr.length - 1 such
that arr[0] < arr[1] < ... < arr[i] > arr[i+1] > ... > arr[arr.length-1]
Given a mountain array `mountainArr`, return the minimum index such that
mountainArr.get(index) == target. If such an index does not exist, return -1.
You cannot access the mountain array directly. You may only access the array
using a MountainArray interface:
- MountainArray.get(k) returns the element of the array at index k (0-indexed)
- MountainArray.length() returns the length of the array
Submissions making more than 100 calls to MountainArray.get will be judged
Wrong Answer.
Examples
--------
```text
Input: array = [1, 2, 3, 4, 5, 3, 1], target = 3
Output: 2
Input: array = [0, 1, 2, 4, 2, 1], target = 3
Output: -1
```
Constraints
-----------
- 3 <= mountain_arr.length() <= 10^4
- 0 <= target <= 10^9
- 0 <= mountain_arr.get(index) <= 10^9
"""
class MountainArray:
"""Mock interface for testing."""
def __init__(self, arr: list):
self._arr = arr
self._calls = 0
def get(self, index: int) -> int:
self._calls += 1
assert self._calls <= 100, "Exceeded 100 API calls"
return self._arr[index]
def length(self) -> int:
return len(self._arr)
class Solution:
def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int:
# Step 1: find peak — binary search where arr[mid] < arr[mid+1] means peak is right
# Step 2: binary search ascending left half [0..peak]
# Step 3: binary search descending right half [peak+1..n-1] (reversed comparisons)
# O(log n) time | O(1) space
n = mountain_arr.length()
# 1. Find peak index
lo, hi = 0, n - 1
while lo < hi:
mid = (lo + hi) // 2
if mountain_arr.get(mid) < mountain_arr.get(mid + 1):
lo = mid + 1
else:
hi = mid
peak = lo
# 2. Binary search ascending left half
lo, hi = 0, peak
while lo <= hi:
mid = (lo + hi) // 2
val = mountain_arr.get(mid)
if val == target:
return mid
elif val < target:
lo = mid + 1
else:
hi = mid - 1
# 3. Binary search descending right half
lo, hi = peak + 1, n - 1
while lo <= hi:
mid = (lo + hi) // 2
val = mountain_arr.get(mid)
if val == target:
return mid
elif val > target:
lo = mid + 1
else:
hi = mid - 1
return -1
if __name__ == "__main__":
s = Solution()
assert s.findInMountainArray(3, MountainArray([1, 2, 3, 4, 5, 3, 1])) == 2
assert s.findInMountainArray(3, MountainArray([0, 1, 2, 4, 2, 1])) == -1
assert s.findInMountainArray(1, MountainArray([1, 2, 3, 4, 5, 3, 1])) == 0
assert s.findInMountainArray(5, MountainArray([1, 2, 3, 4, 5, 3, 1])) == 4
print("All tests passed.") Find Minimum In Rotated Sorted Array ▼ expand
"""
LC 153 - Find Minimum in Rotated Sorted Array
===============================================
Suppose an array of length `n` sorted in ascending order is rotated between 1
and n times. For example, the array nums = [0,1,2,4,5,6,7] might become
[4,5,6,7,0,1,2] if it was rotated 4 times.
Given the sorted rotated array `nums` of unique elements, return the minimum
element of this array.
You must write an algorithm that runs in O(log n) time.
Examples
--------
```text
Input: nums = [3, 4, 5, 1, 2]
Output: 1
Input: nums = [4, 5, 6, 7, 0, 1, 2]
Output: 0
Input: nums = [11, 13, 15, 17]
Output: 11
```
Constraints
-----------
- n == nums.length
- 1 <= n <= 5000
- -5000 <= nums[i] <= 5000
- All the integers of nums are unique
- nums is sorted and rotated between 1 and n times
"""
from typing import List
class Solution:
def method_linear(self, nums: List[int]) -> int:
# O(n) time | O(1) space
return min(nums)
def findMin(self, nums: List[int]) -> int:
# If mid > hi, the minimum is in the right half (rotation point is there)
# Otherwise minimum is in the left half including mid
# O(log n) time | O(1) space
lo, hi = 0, len(nums) - 1
while lo < hi:
mid = (lo + hi) // 2
if nums[mid] > nums[hi]:
lo = mid + 1
else:
hi = mid
return nums[lo]
if __name__ == "__main__":
s = Solution()
assert s.findMin([3, 4, 5, 1, 2]) == 1
assert s.findMin([4, 5, 6, 7, 0, 1, 2]) == 0
assert s.findMin([11, 13, 15, 17]) == 11
assert s.method_linear([3, 4, 5, 1, 2]) == 1
print("All tests passed.") Guess Number Higher Or Lower ▼ expand
"""
LC 374 - Guess Number Higher or Lower
======================================
We are playing the Guess Game. The game is as follows:
I pick a number from 1 to n. You have to guess which number I picked.
Every time you guess wrong, I will tell you whether the number I picked is
higher or lower than your guess.
You call a pre-defined API `guess(num: int) -> int` which returns three
possible results:
-1 : Your guess is higher than the number I picked (i.e. num > pick)
1 : Your guess is lower than the number I picked (i.e. num < pick)
0 : Your guess is equal to the number I picked (i.e. num == pick)
Return the number that I picked.
Examples
--------
```text
Input: n = 10, pick = 6
Output: 6
Input: n = 1, pick = 1
Output: 1
```
Constraints
-----------
- 1 <= n <= 2^31 - 1
- 1 <= pick <= n
"""
_PICK = 0 # set before each test
def guess(num: int) -> int:
if num > _PICK:
return -1
elif num < _PICK:
return 1
return 0
class Solution:
def method_linear(self, n: int) -> int:
# O(n) time | O(1) space
for i in range(1, n + 1):
if guess(i) == 0:
return i
return -1
def guessNumber(self, n: int) -> int:
# Binary search using the guess() API: -1 means too high, 1 means too low
# O(log n) time | O(1) space
lo, hi = 1, n
while lo <= hi:
mid = (lo + hi) // 2
res = guess(mid)
if res == 0:
return mid
elif res == 1:
lo = mid + 1
else:
hi = mid - 1
return -1
if __name__ == "__main__":
import sys
s = Solution()
for n, pick in [(10, 6), (1, 1), (2, 2)]:
globals()["_PICK"] = pick
assert s.guessNumber(n) == pick, f"guessNumber({n}) failed"
globals()["_PICK"] = pick
assert s.method_linear(n) == pick, f"method_linear({n}) failed"
print("All tests passed.") Koko Eating Bananas ▼ expand
"""
LC 875 - Koko Eating Bananas
=============================
Koko loves to eat bananas. There are `n` piles of bananas, the i-th pile has
`piles[i]` bananas. The guards have gone and will come back in `h` hours.
Koko can decide her bananas-per-hour eating speed of `k`. Each hour, she
chooses some pile of bananas and eats `k` bananas from that pile. If the pile
has less than `k` bananas, she eats all of them instead and will not eat any
more bananas during this hour.
Koko likes to eat slowly but still wants to finish eating all the bananas
before the guards return. Return the minimum integer `k` such that she can eat
all the bananas within `h` hours.
Examples
--------
```text
Input: piles = [3, 6, 7, 11], h = 8
Output: 4
Input: piles = [30, 11, 23, 4, 20], h = 5
Output: 30
Input: piles = [30, 11, 23, 4, 20], h = 6
Output: 23
```
Constraints
-----------
- 1 <= piles.length <= 10^4
- piles.length <= h <= 10^9
- 1 <= piles[i] <= 10^9
"""
import math
from typing import List
class Solution:
def _hours_needed(self, piles: List[int], k: int) -> int:
# Compute total hours needed at speed k: ceil(pile/k) per pile
return sum(math.ceil(p / k) for p in piles)
def method_linear(self, piles: List[int], h: int) -> int:
# Try every speed from 1 upward — O(max(piles)*n)
# O(max(piles) * n) time | O(1) space
k = 1
while self._hours_needed(piles, k) > h:
k += 1
return k
def minEatingSpeed(self, piles: List[int], h: int) -> int:
# Binary search on speed: lo=1, hi=max(piles)
# If hours_needed(mid) <= h, mid might be the answer — search left half
# O(n * log(max(piles))) time | O(1) space
lo, hi = 1, max(piles)
while lo < hi:
mid = (lo + hi) // 2
if self._hours_needed(piles, mid) <= h:
hi = mid
else:
lo = mid + 1
return lo
if __name__ == "__main__":
s = Solution()
assert s.minEatingSpeed([3, 6, 7, 11], 8) == 4
assert s.minEatingSpeed([30, 11, 23, 4, 20], 5) == 30
assert s.minEatingSpeed([30, 11, 23, 4, 20], 6) == 23
assert s.method_linear([3, 6, 7, 11], 8) == 4
print("All tests passed.") Median Of Two Sorted Arrays ▼ expand
"""
LC 4 - Median of Two Sorted Arrays
=====================================
Given two sorted arrays `nums1` and `nums2` of size `m` and `n` respectively,
return the median of the two sorted arrays.
The overall run time complexity should be O(log(m + n)).
Examples
--------
```text
Input: nums1 = [1, 3], nums2 = [2]
Output: 2.0
Explanation: merged = [1, 2, 3], median = 2.0
Input: nums1 = [1, 2], nums2 = [3, 4]
Output: 2.5
Explanation: merged = [1, 2, 3, 4], median = (2 + 3) / 2 = 2.5
```
Constraints
-----------
- nums1.length == m
- nums2.length == n
- 0 <= m, n <= 1000
- 1 <= m + n <= 2000
- -10^6 <= nums1[i], nums2[i] <= 10^6
"""
from typing import List
class Solution:
def method_merge(self, nums1: List[int], nums2: List[int]) -> float:
# Merge both arrays, sort, then find median by index — O(m+n)
# O(m + n) time | O(m + n) space
merged = sorted(nums1 + nums2)
total = len(merged)
mid = total // 2
if total % 2 == 1:
return float(merged[mid])
return (merged[mid - 1] + merged[mid]) / 2.0
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
# Binary search on partition of smaller array; derive partition of larger
# Valid partition: left1<=right2 and left2<=right1 (cross-check boundaries)
# Odd total: median is min of right halves; even: average of inner boundary
# O(log(min(m, n))) time | O(1) space
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
half = (m + n) // 2
lo, hi = 0, m
while lo <= hi:
i = (lo + hi) // 2 # partition in nums1
j = half - i # partition in nums2
left1 = nums1[i - 1] if i > 0 else float('-inf')
right1 = nums1[i] if i < m else float('inf')
left2 = nums2[j - 1] if j > 0 else float('-inf')
right2 = nums2[j] if j < n else float('inf')
if left1 <= right2 and left2 <= right1:
if (m + n) % 2 == 1:
return float(min(right1, right2))
return (max(left1, left2) + min(right1, right2)) / 2.0
elif left1 > right2:
hi = i - 1
else:
lo = i + 1
return 0.0
if __name__ == "__main__":
s = Solution()
assert s.findMedianSortedArrays([1, 3], [2]) == 2.0
assert s.findMedianSortedArrays([1, 2], [3, 4]) == 2.5
assert s.findMedianSortedArrays([], [1]) == 1.0
assert s.method_merge([1, 3], [2]) == 2.0
assert s.method_merge([1, 2], [3, 4]) == 2.5
print("All tests passed.") Search A 2d Matrix ▼ expand
"""
LC 74 - Search a 2D Matrix
===========================
You are given an m x n integer matrix `matrix` with the following two
properties:
- Each row is sorted in non-decreasing order.
- The first integer of each row is greater than the last integer of the
previous row.
Given an integer `target`, return true if `target` is in `matrix`, or false
otherwise.
Examples
--------
```text
Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
Output: true
Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
Output: false
```
Constraints
-----------
- m == matrix.length
- n == matrix[i].length
- 1 <= m, n <= 100
- -10^4 <= matrix[i][j], target <= 10^4
"""
from typing import List
class Solution:
def method_brute_force(self, matrix: List[List[int]], target: int) -> bool:
# O(m*n) time | O(1) space
for row in matrix:
for val in row:
if val == target:
return True
return False
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
# Treat the 2D matrix as a 1D sorted array using index math
# mid // n gives row, mid % n gives column
# O(log(m*n)) time | O(1) space
m, n = len(matrix), len(matrix[0])
lo, hi = 0, m * n - 1
while lo <= hi:
mid = (lo + hi) // 2
val = matrix[mid // n][mid % n]
if val == target:
return True
elif val < target:
lo = mid + 1
else:
hi = mid - 1
return False
if __name__ == "__main__":
s = Solution()
mat = [[1, 3, 5, 7], [10, 11, 16, 20], [23, 30, 34, 60]]
assert s.searchMatrix(mat, 3) is True
assert s.searchMatrix(mat, 13) is False
assert s.method_brute_force(mat, 3) is True
assert s.method_brute_force(mat, 13) is False
print("All tests passed.") Search In Rotated Sorted Array ▼ expand
"""
LC 33 - Search in Rotated Sorted Array
========================================
There is an integer array `nums` sorted in ascending order (with distinct
values). Prior to being passed to your function, `nums` is possibly rotated at
an unknown pivot index k such that the resulting array is
[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]].
Given the array `nums` after the possible rotation and an integer `target`,
return the index of `target` if it is in `nums`, or -1 if it is not in `nums`.
You must write an algorithm with O(log n) runtime complexity.
Examples
--------
```text
Input: nums = [4, 5, 6, 7, 0, 1, 2], target = 0
Output: 4
Input: nums = [4, 5, 6, 7, 0, 1, 2], target = 3
Output: -1
Input: nums = [1], target = 0
Output: -1
```
Constraints
-----------
- 1 <= nums.length <= 5000
- -10^4 <= nums[i] <= 10^4
- All values of nums are unique
- nums is an ascending array that is possibly rotated
- -10^4 <= target <= 10^4
"""
from typing import List
class Solution:
def method_linear(self, nums: List[int], target: int) -> int:
# O(n) time | O(1) space
for i, v in enumerate(nums):
if v == target:
return i
return -1
def search(self, nums: List[int], target: int) -> int:
# Determine which half is sorted by comparing nums[lo] to nums[mid]
# Check if target falls in the sorted half; if yes search there, else other half
# O(log n) time | O(1) space
lo, hi = 0, len(nums) - 1
while lo <= hi:
mid = (lo + hi) // 2
if nums[mid] == target:
return mid
if nums[lo] <= nums[mid]: # left half sorted
if nums[lo] <= target < nums[mid]:
hi = mid - 1
else:
lo = mid + 1
else: # right half sorted
if nums[mid] < target <= nums[hi]:
lo = mid + 1
else:
hi = mid - 1
return -1
if __name__ == "__main__":
s = Solution()
assert s.search([4, 5, 6, 7, 0, 1, 2], 0) == 4
assert s.search([4, 5, 6, 7, 0, 1, 2], 3) == -1
assert s.search([1], 0) == -1
assert s.method_linear([4, 5, 6, 7, 0, 1, 2], 0) == 4
print("All tests passed.") Search In Rotated Sorted Array Ii ▼ expand
"""
LC 81 - Search in Rotated Sorted Array II
==========================================
There is an integer array `nums` sorted in non-decreasing order (not
necessarily with distinct values). Before being passed to your function, `nums`
is rotated at an unknown pivot index k.
Given the array `nums` after the possible rotation and an integer `target`,
return true if `target` is in `nums`, or false if it is not in `nums`.
You must decrease the overall operation steps as much as possible.
Examples
--------
```text
Input: nums = [2, 5, 6, 0, 0, 1, 2], target = 0
Output: true
Input: nums = [2, 5, 6, 0, 0, 1, 2], target = 3
Output: false
```
Constraints
-----------
- 1 <= nums.length <= 5000
- -10^4 <= nums[i] <= 10^4
- nums is sorted and rotated at some pivot
- -10^4 <= target <= 10^4
"""
from typing import List
class Solution:
def method_linear(self, nums: List[int], target: int) -> bool:
# O(n) time | O(1) space
return target in nums
def search(self, nums: List[int], target: int) -> bool:
# Same as rotated search but duplicates can obscure which half is sorted
# When nums[lo]==nums[mid]==nums[hi], shrink both ends by 1 to break tie
# O(log n) average, O(n) worst time | O(1) space
lo, hi = 0, len(nums) - 1
while lo <= hi:
mid = (lo + hi) // 2
if nums[mid] == target:
return True
# can't determine sorted half — shrink bounds
if nums[lo] == nums[mid] == nums[hi]:
lo += 1
hi -= 1
elif nums[lo] <= nums[mid]: # left half sorted
if nums[lo] <= target < nums[mid]:
hi = mid - 1
else:
lo = mid + 1
else: # right half sorted
if nums[mid] < target <= nums[hi]:
lo = mid + 1
else:
hi = mid - 1
return False
if __name__ == "__main__":
s = Solution()
assert s.search([2, 5, 6, 0, 0, 1, 2], 0) is True
assert s.search([2, 5, 6, 0, 0, 1, 2], 3) is False
assert s.search([1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 13) is True
assert s.method_linear([2, 5, 6, 0, 0, 1, 2], 0) is True
print("All tests passed.") Search Insert Position ▼ expand
"""
LC 35 - Search Insert Position
================================
Given a sorted array of distinct integers and a target value, return the index
if the target is found. If not, return the index where it would be inserted in
order.
You must write an algorithm with O(log n) runtime complexity.
Examples
--------
```text
Input: nums = [1, 3, 5, 6], target = 5
Output: 2
Input: nums = [1, 3, 5, 6], target = 2
Output: 1
Input: nums = [1, 3, 5, 6], target = 7
Output: 4
```
Constraints
-----------
- 1 <= nums.length <= 10^4
- -10^4 <= nums[i] <= 10^4
- nums contains distinct values sorted in ascending order
- -10^4 <= target <= 10^4
"""
from typing import List
class Solution:
def method_linear(self, nums: List[int], target: int) -> int:
# O(n) time | O(1) space
for i, v in enumerate(nums):
if v >= target:
return i
return len(nums)
def searchInsert(self, nums: List[int], target: int) -> int:
# Binary search; lo converges to the insertion point when target not found
# O(log n) time | O(1) space
lo, hi = 0, len(nums) - 1
while lo <= hi:
mid = (lo + hi) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
lo = mid + 1
else:
hi = mid - 1
return lo
if __name__ == "__main__":
s = Solution()
assert s.searchInsert([1, 3, 5, 6], 5) == 2
assert s.searchInsert([1, 3, 5, 6], 2) == 1
assert s.searchInsert([1, 3, 5, 6], 7) == 4
assert s.method_linear([1, 3, 5, 6], 5) == 2
assert s.method_linear([1, 3, 5, 6], 2) == 1
assert s.method_linear([1, 3, 5, 6], 7) == 4
print("All tests passed.") Split Array Largest Sum ▼ expand
"""
LC 410 - Split Array Largest Sum
==================================
Given an integer array `nums` and an integer `k`, split `nums` into `k`
non-empty subarrays such that the largest sum of any subarray is minimized.
Return the minimized largest sum of the split.
Examples
--------
```text
Input: nums = [7, 2, 5, 10, 8], k = 2
Output: 18
Explanation: Split into [7,2,5] and [10,8]. Largest sum = max(14,18) = 18.
Input: nums = [1, 2, 3, 4, 5], k = 2
Output: 9
```
Constraints
-----------
- 1 <= nums.length <= 1000
- 0 <= nums[i] <= 10^6
- 1 <= k <= min(50, nums.length)
"""
from typing import List
class Solution:
def method_dp(self, nums: List[int], k: int) -> int:
# O(n^2 * k) time | O(n * k) space
n = len(nums)
prefix = [0] * (n + 1)
for i, v in enumerate(nums):
prefix[i + 1] = prefix[i] + v
dp = [[float('inf')] * (k + 1) for _ in range(n + 1)]
dp[0][0] = 0
for i in range(1, n + 1):
for j in range(1, k + 1):
for m in range(i):
dp[i][j] = min(dp[i][j], max(dp[m][j - 1], prefix[i] - prefix[m]))
return dp[n][k]
def splitArray(self, nums: List[int], k: int) -> int:
# Binary search on the answer (max subarray sum): lo=max(nums), hi=sum(nums)
# Check if we can split into <= m subarrays with max sum <= mid
# O(n * log(sum(nums))) time | O(1) space
def can_split(cap: int) -> bool:
parts, curr = 1, 0
for v in nums:
if curr + v > cap:
parts += 1
curr = 0
curr += v
return parts <= k
lo, hi = max(nums), sum(nums)
while lo < hi:
mid = (lo + hi) // 2
if can_split(mid):
hi = mid
else:
lo = mid + 1
return lo
if __name__ == "__main__":
s = Solution()
assert s.splitArray([7, 2, 5, 10, 8], 2) == 18
assert s.splitArray([1, 2, 3, 4, 5], 2) == 9
assert s.method_dp([7, 2, 5, 10, 8], 2) == 18
assert s.method_dp([1, 2, 3, 4, 5], 2) == 9
print("All tests passed.") Sqrtx ▼ expand
"""
LC 69 - Sqrt(x)
================
Given a non-negative integer `x`, return the square root of `x` rounded down
to the nearest integer. The returned integer should be non-negative as well.
You must not use any built-in exponent function or operator (e.g., pow(x, 0.5)
or x ** 0.5).
Examples
--------
```text
Input: x = 4
Output: 2
Input: x = 8
Output: 2
Explanation: The square root of 8 is 2.828..., rounded down to 2.
```
Constraints
-----------
- 0 <= x <= 2^31 - 1
"""
class Solution:
def method_linear(self, x: int) -> int:
# O(sqrt(x)) time | O(1) space
i = 0
while (i + 1) * (i + 1) <= x:
i += 1
return i
def mySqrt(self, x: int) -> int:
# Binary search: find largest integer whose square <= x
# When mid*mid <= x, record mid as candidate and search right for larger
# O(log x) time | O(1) space
if x < 2:
return x
lo, hi = 1, x // 2
while lo <= hi:
mid = (lo + hi) // 2
sq = mid * mid
if sq == x:
return mid
elif sq < x:
lo = mid + 1
else:
hi = mid - 1
return hi
if __name__ == "__main__":
s = Solution()
for x, expected in [(0, 0), (1, 1), (4, 2), (8, 2), (9, 3), (2147395600, 46340)]:
assert s.mySqrt(x) == expected, f"mySqrt({x}) = {s.mySqrt(x)}, expected {expected}"
assert s.method_linear(x) == expected, f"method_linear({x}) failed"
print("All tests passed.") Time Based Key Value Store ▼ expand
"""
LC 981 - Time Based Key-Value Store
=====================================
Design a time-based key-value data structure that can store multiple values
for the same key at different time stamps and retrieve the key's value at a
certain timestamp.
Implement the TimeMap class:
- TimeMap() Initializes the object of the data structure.
- void set(String key, String value, int timestamp) Stores the key with the
value at the given time timestamp.
- String get(String key, int timestamp) Returns a value such that set was
called previously with timestamp_prev <= timestamp. If there are multiple
such values, it returns the value associated with the largest
timestamp_prev. If there are no values, it returns "".
Examples
--------
```text
Input:
["TimeMap","set","get","get","set","get","get"]
[[],["foo","bar",1],["foo",1],["foo",3],["foo","bar2",4],["foo",4],["foo",5]]
Output:
[null, null, "bar", "bar", null, "bar2", "bar2"]
Explanation:
set("foo","bar",1) -> stores foo=bar at t=1
get("foo",1) -> "bar"
get("foo",3) -> "bar" (largest ts <= 3 is 1)
set("foo","bar2",4) -> stores foo=bar2 at t=4
get("foo",4) -> "bar2"
get("foo",5) -> "bar2" (largest ts <= 5 is 4)
```
Constraints
-----------
- 1 <= key.length, value.length <= 100
- key and value consist of lowercase English letters and digits
- 1 <= timestamp <= 10^7
- All calls to set will have strictly increasing timestamp values
- At most 2 * 10^5 calls will be made to set and get
"""
import bisect
from collections import defaultdict
class TimeMapLinear:
"""Approach 1: linear scan — O(n) per get."""
def __init__(self):
self.store: dict = defaultdict(list) # key -> [(ts, val)]
def set(self, key: str, value: str, timestamp: int) -> None:
# Append (timestamp, value) to the list for this key — timestamps are increasing
self.store[key].append((timestamp, value))
def get(self, key: str, timestamp: int) -> str:
# Binary search for the largest timestamp <= given timestamp
# bisect_right finds insertion point; step back one to get the floor
# O(n) time | O(1) space per call
result = ""
for ts, val in self.store[key]:
if ts <= timestamp:
result = val
return result
class TimeMap:
"""Approach 2: binary search — O(log n) per get."""
def __init__(self):
self.store: dict = defaultdict(list) # key -> [(ts, val)]
def set(self, key: str, value: str, timestamp: int) -> None:
# Append (timestamp, value) to the list for this key — timestamps are increasing
self.store[key].append((timestamp, value))
def get(self, key: str, timestamp: int) -> str:
# Binary search for the largest timestamp <= given timestamp
# bisect_right finds insertion point; step back one to get the floor
# O(log n) time | O(1) space per call
entries = self.store[key]
idx = bisect.bisect_right(entries, (timestamp, chr(127))) - 1
return entries[idx][1] if idx >= 0 else ""
if __name__ == "__main__":
for Cls in (TimeMapLinear, TimeMap):
tm = Cls()
tm.set("foo", "bar", 1)
assert tm.get("foo", 1) == "bar"
assert tm.get("foo", 3) == "bar"
tm.set("foo", "bar2", 4)
assert tm.get("foo", 4) == "bar2"
assert tm.get("foo", 5) == "bar2"
assert tm.get("foo", 0) == ""
print("All tests passed.") Binary Search
Classic binary search · Binary search on answer space
2
lo
0
5
1
8
2
12
3
16
4
23
5
38
6
45
7
67
8
78
9
89
10
95
hi
11
Sorted array ready. Searching for 23. Press "Next Step".