Binary Search — Worked Examples
Scope — The problem set for binary_search.md: each problem filed under the index-space template it reduces to, as one problem card with one canonical solution, plus the few answer-space problems that are not worked on binary_search_on_answer.md. See also — parent sheet: binary_search.md — the loop invariants, the boundary templates and the “which template” decision table; binary_search_on_answer.md — every problem that binary searches the answer rather than an index. Neighbouring sheets: n_sum.md — the converging-pointer answer to LC 167; sort.md — the sort-then-LIS answer to LC 354; patience_sorting.md — the problems that reduce to LIS; matrix.md — the 2D-grid family.
LeetCode Problem Lists
Overview
binary_search.md holds the eight index-space templates; this sheet is the problems. Each section below mirrors one template, in the core sheet’s order, and every card names the one step it adds to that template. Templates whose anchor problem is the whole lesson (Template 4, 6 and 8) have no section here. The last section holds the answer-space problems that reduce to binary_search_on_answer.md’s cards, and the pointer table lists the problems worked on another sheet.
Index
Lives elsewhere — worked once, on the sheet that owns it:
| LC | Problem | Home | Why there |
|---|---|---|---|
| 162 | Find Peak Element | binary_search.md § Template 7 | the template’s anchor problem |
| 300 | Longest Increasing Subsequence | binary_search.md § Template 5 | the template’s anchor problem |
| 354 | Russian Doll Envelopes | sort.md § Template 9 | the sort-with-a-tie-rule is the lesson; the LIS after it is Template 5 |
| 167 | Two Sum II — Input Array Is Sorted | n_sum.md | two pointers is O(n); a binary search per element is O(n log n) |
| 209 | Minimum Size Subarray Sum | sliding_window.md § Template 4 | the window is O(n); prefix sums + a lower bound per start is the O(n log n) follow-up |
| 69 | Sqrt(x) | math.md § Integer Square Root | binary search on the answer and Newton’s method, side by side |
| 528 | Random Pick with Weight | math.md § Randomized Sampling | prefix sums + a lower bound on a uniform draw |
| 981 | Time Based Key-Value Store | design_examples.md | a per-key sorted list, queried with upper bound − 1 |
| 287 | Find the Duplicate Number | binary_search_on_answer.md § Find the Duplicate Number | binary search over the value domain, with a pigeonhole count |
Exact Match Examples
Median of Two Sorted Arrays — LC 4 Priority 5 of 5 — Must know — expect it in almost every loop
Key point — search the cut in the shorter array, not a value. A cut
iinnums1forces the cutj = half − iinnums2; the cut is right when both left maxima are<=both right minima, and when it is wrong, which inequality failed says which way to move.
Reduces to: binary_search § Template 1 — the same closed-interval loop with three outcomes (found, go left, go right), over cut positions [0, m] instead of indices, and a cross-array test instead of nums[mid] == target.
# python
# LC 4 - Median of Two Sorted Arrays
# IDEA: binary search the cut i in the shorter array; j = half - i; stop when left maxima <= right minima
# time = O(log(min(m, n))), space = O(1)
def findMedianSortedArrays(nums1, nums2):
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1 # search the shorter array
m, n = len(nums1), len(nums2)
half = (m + n + 1) // 2 # size of the left half (the larger one when odd)
left, right = 0, m # i = how many of nums1 go to the left half
while left <= right:
i = left + (right - left) // 2
j = half - i
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: # a valid cut
if (m + n) % 2 == 1:
return max(left1, left2)
return (max(left1, left2) + min(right1, right2)) / 2
if left1 > right2:
right = i - 1 # took too many from nums1
else:
left = i + 1 # took too few from nums1
return 0.0 # unreachable for sorted input
// java
// LC 4 - Median of Two Sorted Arrays
// IDEA: binary search the cut in the shorter array; the other cut follows from the half size
// time = O(log(min(m, n))), space = O(1)
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if (nums1.length > nums2.length) return findMedianSortedArrays(nums2, nums1);
int m = nums1.length, n = nums2.length;
int half = (m + n + 1) / 2;
int left = 0, right = m;
while (left <= right) {
int i = left + (right - left) / 2;
int j = half - i;
int left1 = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
int right1 = i == m ? Integer.MAX_VALUE : nums1[i];
int left2 = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
int right2 = j == n ? Integer.MAX_VALUE : nums2[j];
if (left1 <= right2 && left2 <= right1) {
if ((m + n) % 2 == 1) return Math.max(left1, left2);
return (Math.max(left1, left2) + (double) Math.min(right1, right2)) / 2.0;
} else if (left1 > right2) {
right = i - 1;
} else {
left = i + 1;
}
}
return 0.0;
}
Traps
- Searching the longer array lets
j = half − igo negative:nums1 = [1, 2, 3, 4],nums2 = [5]can probei = 4, soj = −1. Swap sonums1is the shorter one. - An empty side needs the
±infsentinels:nums1 = [],nums2 = [1]hasi = 0, so there is nonums1[i − 1]. - In Java,
max + minof two large ints overflows before the division: add asdouble.
Similar — LC 2040 (k-th smallest product: binary_search_on_answer § Template 4)
Lower and Upper Bound Examples
First Bad Version — LC 278
Key point —
isBadVersionreadsFalse … False True … True, so the first bad version is Template 2’sfindLeftwith the API call as the comparison. There is no array and no target.
Reduces to: binary_search § Template 2 — findLeft with isBadVersion(mid) in place of nums[mid] >= target; the answer always exists, so no validation.
# python
# LC 278 - First Bad Version
# IDEA: lower bound on the boolean API: the first version where isBadVersion turns True
# time = O(log n), space = O(1)
def firstBadVersion(n, isBadVersion):
left, right = 1, n
while left < right:
mid = left + (right - left) // 2
if isBadVersion(mid):
right = mid # mid is bad: the first bad one is mid or earlier
else:
left = mid + 1 # mid is good: the first bad one is after it
return left
// java
// LC 278 - First Bad Version
// IDEA: lower bound on the boolean API
// time = O(log n), space = O(1)
public int firstBadVersion(int n) {
int left = 1, right = n;
while (left < right) {
int mid = left + (right - left) / 2; // (left + right) overflows when n is near 2^31
if (isBadVersion(mid)) right = mid;
else left = mid + 1;
}
return left;
}
Traps
(left + right) / 2overflows in Java:n = 2126753390is one of LeetCode’s own tests.
Similar — LC 374 (guess API, exact match) · LC 35 (insert position)
Find Smallest Letter Greater Than Target — LC 744
Key point — “smallest letter strictly greater than target” is an upper bound (the first letter
> target). When none is greater, the answer wraps toletters[0], which% ndoes for free.
Reduces to: binary_search § Template 2 — findRight + 1 (upper bound) on characters, then wrap the index.
# python
# LC 744 - Find Smallest Letter Greater Than Target
# IDEA: upper bound (first letter > target) on a half-open range, then wrap with % n
# time = O(log n), space = O(1)
def nextGreatestLetter(letters, target):
left, right = 0, len(letters) # half-open [left, right): right == n means "none"
while left < right:
mid = left + (right - left) // 2
if letters[mid] <= target:
left = mid + 1 # not strictly greater: the answer is to the right
else:
right = mid
return letters[left % len(letters)] # left == n wraps around to letters[0]
Traps
letters[mid] < targetfinds the first letter>= target:letters = ["c","f","j"],target = "c"returns"c"instead of"f".- Duplicates are allowed:
["e","e","e","n"],target = "e"must skip every"e", which<=does.
Check If a Number Is Majority Element in a Sorted Array — LC 1150
Key point — in a sorted array a value occupies one contiguous run. Find where
target’s run starts; it is a majority exactly when the elementn // 2places later is stilltarget.
Reduces to: binary_search § Template 2 — one findLeft, then a single O(1) check instead of a second search.
# python
# LC 1150 - Check If a Number Is Majority Element in a Sorted Array
# IDEA: lower bound = start of target's run; majority iff nums[start + n // 2] == target
# time = O(log n), space = O(1)
def isMajorityElement(nums, target):
n = len(nums)
left, right = 0, n
while left < right: # first index with nums[i] >= target
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid
end = left + n // 2 # a run longer than n / 2 must still cover this index
return end < n and nums[end] == target
// java
// LC 1150 - Check If a Number Is Majority Element in a Sorted Array
// IDEA: lower bound = start of target's run; majority iff nums[start + n / 2] == target
// time = O(log n), space = O(1)
public boolean isMajorityElement(int[] nums, int target) {
int n = nums.length;
int left = 0, right = n;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) left = mid + 1;
else right = mid;
}
int end = left + n / 2;
return end < n && nums[end] == target;
}
nums = [2,4,5,5,5,5,5,6,6], target = 5, n = 9
lower bound of 5 -> index 2
index 2 + 9 // 2 = 6, nums[6] = 5 == target -> true
index: 0 1 2 3 4 5 6 7 8
value: 2 4 [5] 5 5 5 [5] 6 6
start ^ still 5 iff the run is longer than n / 2
Traps
- Skipping the
end < ncheck:nums = [1, 2],target = 2starts at 1, and1 + 1 = 2is out of range. - Target absent: the lower bound lands on a different value, and the check still returns
Falsewithout a separate “found” test.
Similar — LC 34 (both ends of the run) · LC 169 (unsorted majority: Boyer–Moore)
Single Element in a Sorted Array — LC 540
Key point — before the single element, every pair starts at an even index; after it, at an odd one. So “the pair at even
midis intact” isTrue … True False … False, and the single element is the first even index where it fails.
Reduces to: binary_search § Template 2 — a lower bound on a parity predicate, probing even indices only.
# python
# LC 540 - Single Element in a Sorted Array
# IDEA: probe even mids; an intact pair (nums[mid] == nums[mid+1]) means the single is to the right
# time = O(log n), space = O(1)
def singleNonDuplicate(nums):
left, right = 0, len(nums) - 1
while left < right:
mid = left + (right - left) // 2
if mid % 2 == 1:
mid -= 1 # align mid to the start of a pair
if nums[mid] == nums[mid + 1]:
left = mid + 2 # pair intact: the single is after this pair
else:
right = mid # pair broken: the single is at mid or before
return nums[left]
// java
// LC 540 - Single Element in a Sorted Array
// IDEA: probe even mids; an intact pair means the single is to the right
// time = O(log n), space = O(1)
public int singleNonDuplicate(int[] nums) {
int left = 0, right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (mid % 2 == 1) mid--;
if (nums[mid] == nums[mid + 1]) left = mid + 2;
else right = mid;
}
return nums[left];
}
Traps
- Without aligning
midto even,nums[mid] == nums[mid + 1]can match the second half of a pair:[1,1,2,3,3,4,4]probesmid = 3, sees3 == 3, jumps toleft = 5and returns 4 instead of 2. mid + 1is in range becauseleft < rightandrightis always even (the length is odd).
Similar — LC 136 (unsorted: XOR in O(n))
Find K Closest Elements — LC 658
Key point — the answer is a window
arr[i .. i + k − 1], so search the window’s start. Compare the two elements that a shift would swap: ifx − arr[i] > arr[i + k] − x, the window starting ati + 1is at least as close, and that test turns fromTruetoFalseonce.
Reduces to: binary_search § Template 2 — a lower bound over window starts [0, n − k], with the predicate “shifting right is no better”.
# python
# LC 658 - Find K Closest Elements
# IDEA: binary search the window start i in [0, n - k]; move right while arr[i] is farther than arr[i + k]
# time = O(log(n - k) + k), space = O(1) besides the output
def findClosestElements(arr, k, x):
left, right = 0, len(arr) - k
while left < right:
mid = left + (right - left) // 2
if x - arr[mid] > arr[mid + k] - x:
left = mid + 1 # arr[mid] is farther than arr[mid + k]: start later
else:
right = mid # arr[mid] is at least as close (ties keep the smaller)
return arr[left:left + k]
Traps
abs(x - arr[mid]) > abs(arr[mid + k] - x)breaks when both sit on the same side ofx:arr = [1,1,2,2,2,2,2,3,3],k = 3,x = 3returns[2,2,2]instead of[2,3,3]. Keep the signed differences.>=instead of>breaks ties toward the larger value; the problem wants the smaller one:arr = [1,2,3,4,5],k = 4,x = 3must return[1,2,3,4].
Second approach — shrink from both ends (drop whichever end is farther) until k remain:
O(n − k), the version to give when the binary-search predicate does not come to mind. Sorting by
distance (sort.md) is O(n log n).
Find Right Interval — LC 436 Priority 4 of 5 — High value — a gap here costs you rounds
Key point — for each interval, the answer is the smallest start
>= end_iover all intervals: a lower bound over the sorted starts. The answer must be the original index, so sort(start, index)pairs and read the index off the pair the search lands on.
Reduces to: binary_search § Template 2 — one findLeft per interval, on starts sorted together with their original indices.
# python
# LC 436 - Find Right Interval
# IDEA: sort (start, original index) once; per interval, lower-bound end_i among the starts
# time = O(n log n), space = O(n)
import bisect
def findRightInterval(intervals):
pairs = sorted((start, i) for i, (start, _) in enumerate(intervals))
starts = [start for start, _ in pairs] # bisect needs a plain sorted list
result = []
for _, end in intervals:
j = bisect.bisect_left(starts, end) # findLeft: first start >= end
result.append(pairs[j][1] if j < len(pairs) else -1)
return result
intervals = [[3,4],[2,3],[1,2]] original indices 0, 1, 2
pairs = [(1,2), (2,1), (3,0)] sorted by start
[3,4]: end 4 -> no start >= 4 -> -1
[2,3]: end 3 -> first start >= 3 = (3,0) -> 0
[1,2]: end 2 -> first start >= 2 = (2,1) -> 1
Traps
- Sorting the intervals themselves loses the index the answer needs: pair each start with its index before sorting.
- An interval may be its own right interval:
[[1,1]]returns[0](start1 >= end 1). - A nested scan is O(n²) and times out at n = 2·10⁴; the whole point is one O(log n) search per interval.
Similar — LC 35 (the bare lower bound) · LC 2300 (lower bound per spell, count the suffix) · LC 1268 (lower bound the growing prefix)
Rotated Array Minimum Examples
Find Minimum in Rotated Sorted Array II — LC 154
Key point — duplicates add a third case:
nums[mid] == nums[right]says nothing about which runmidis in, butnums[right]is not the unique minimum then (nums[mid]equals it), soright -= 1drops it safely. The worst case becomes O(n).
Reduces to: binary_search § Template 3 — the same half-open squeeze against nums[right], plus a one-step shrink on equality.
# python
# LC 154 - Find Minimum in Rotated Sorted Array II
# IDEA: compare mid with right; on equality the right end is a duplicate, so drop it
# time = O(log n) average, O(n) worst case (all equal); space = O(1)
def findMin(nums):
left, right = 0, len(nums) - 1
while left < right:
mid = left + (right - left) // 2
if nums[mid] < nums[right]:
right = mid # mid is in the right run: min is at mid or left of it
elif nums[mid] > nums[right]:
left = mid + 1 # mid is in the left run: min is after mid
else:
right -= 1 # equal: nums[right] has a copy at mid, drop it
return nums[left]
// java
// LC 154 - Find Minimum in Rotated Sorted Array II
// IDEA: compare mid with right; on equality drop the right end
// time = O(log n) average, O(n) worst case; space = O(1)
public int findMin(int[] nums) {
int left = 0, right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < nums[right]) right = mid;
else if (nums[mid] > nums[right]) left = mid + 1;
else right--;
}
return nums[left];
}
Traps
- Comparing with
nums[left]instead:[1, 3, 3]is unrotated,nums[mid] >= nums[left]sends the search right and misses the1at index 0. - The O(n) worst case is real:
[1,1,1,1,0,1,1]cannot be decided by any probe. Say so when asked.
Similar — LC 81 (search with duplicates: the same right -= 1 fallback on Template 4)
Peak Finding Examples
Peak Index in a Mountain Array — LC 852
Key point — a mountain has exactly one peak, so Template 7 needs no change: walk toward the higher neighbour of
miduntil the window is one index.
Reduces to: binary_search § Template 7 — unchanged; the mountain guarantee makes the peak unique.
# python
# LC 852 - Peak Index in a Mountain Array
# IDEA: half-open squeeze toward the higher neighbour; the window always holds the peak
# time = O(log n), space = O(1)
def peakIndexInMountainArray(arr):
left, right = 0, len(arr) - 1
while left < right:
mid = left + (right - left) // 2
if arr[mid] < arr[mid + 1]:
left = mid + 1 # rising: the peak is to the right
else:
right = mid # falling: the peak is mid or to the left
return left
Similar — LC 162 (any peak, no mountain guarantee) · LC 1095 (below) · LC 1901 (2D peak)
Find in Mountain Array — LC 1095 Priority 5 of 5 — Must know — expect it in almost every loop
Key point — a mountain is two sorted runs glued at the peak. Find the peak with Template 7, then search the ascending left run first (the smallest index wins), and only then the descending right run, with the comparison flipped. Three O(log n) searches stay under the 100-call cap on
get().
Reduces to: binary_search § Template 7 — the peak search, then Template 1 twice: as written on the left run, and with the direction flipped on the right run.
# python
# LC 1095 - Find in Mountain Array
# IDEA: peak via the slope search, then an ascending search on [0, peak] and a descending one on [peak + 1, n - 1]
# time = O(log n), space = O(1)
class Solution:
def findInMountainArray(self, target, mountain_arr):
n = mountain_arr.length()
left, right = 0, n - 1 # 1) the peak
while left < right:
mid = left + (right - left) // 2
if mountain_arr.get(mid) < mountain_arr.get(mid + 1):
left = mid + 1
else:
right = mid
peak = left
index = self.search(mountain_arr, target, 0, peak, True) # 2) ascending half first
if index != -1:
return index
return self.search(mountain_arr, target, peak + 1, n - 1, False) # 3) descending half
def search(self, arr, target, left, right, ascending):
while left <= right:
mid = left + (right - left) // 2
value = arr.get(mid)
if value == target:
return mid
if (value < target) == ascending: # ascending: too small -> right; descending: too big -> right
left = mid + 1
else:
right = mid - 1
return -1
// java
// LC 1095 - Find in Mountain Array
// IDEA: peak via the slope search, then an ascending and a descending Template 1 search
// time = O(log n), space = O(1)
class Solution {
public int findInMountainArray(int target, MountainArray arr) {
int n = arr.length();
int left = 0, right = n - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (arr.get(mid) < arr.get(mid + 1)) left = mid + 1;
else right = mid;
}
int peak = left;
int index = search(arr, target, 0, peak, true);
if (index != -1) return index;
return search(arr, target, peak + 1, n - 1, false);
}
private int search(MountainArray arr, int target, int left, int right, boolean ascending) {
while (left <= right) {
int mid = left + (right - left) / 2;
int value = arr.get(mid);
if (value == target) return mid;
if ((value < target) == ascending) left = mid + 1;
else right = mid - 1;
}
return -1;
}
}
arr = [1,2,3,4,5,3,1], target = 3
peak search -> peak = 4 (value 5)
ascending [0, 4]: mid 2 -> 3 == target -> return 2 (index 5 also holds 3, but 2 is smaller)
Traps
- Searching the right run first returns the larger index:
[1,2,3,4,5,3,1],target = 3must be 2, not 5. - Using the ascending comparison on the right run walks the wrong way:
[0,5,3,1],target = 1is never found. - A linear scan for the peak exceeds the call budget at n = 10⁴.
Similar — LC 852 (the peak alone) · LC 33 (two sorted runs, split by rotation: binary_search § Template 4)
Search on the Answer Examples
Valid Perfect Square — LC 367
Key point — search the root, not the array: the smallest
rwithr * r >= numis the firstTrueof a monotone predicate over[1, num].numis a perfect square exactly when thatrsquares to it.
Reduces to: binary_search_on_answer § Template 1 — smallest feasible value with feasible(r) = r * r >= num, then one equality check.
# python
# LC 367 - Valid Perfect Square
# IDEA: first r in [1, num] with r * r >= num; a perfect square iff r * r == num
# time = O(log num), space = O(1)
def isPerfectSquare(num):
left, right = 1, num
while left < right:
mid = left + (right - left) // 2
if mid * mid >= num:
right = mid # mid is big enough: the root is mid or smaller
else:
left = mid + 1
return left * left == num
// java
// LC 367 - Valid Perfect Square
// IDEA: first r with r * r >= num, in long to avoid overflow
// time = O(log num), space = O(1)
public boolean isPerfectSquare(int num) {
long left = 1, right = num;
while (left < right) {
long mid = left + (right - left) / 2;
if (mid * mid >= num) right = mid;
else left = mid + 1;
}
return left * left == num;
}
Traps
int mid * midoverflows in Java oncemid > 46340:num = 2147483647probesmid ≈ 10⁹. Uselong.sqrtfrom the library is banned by the statement; Newton’s iteration is the other accepted answer (math.md § Integer Square Root).
Similar — LC 69 (the floor root: the last r with r * r <= x) · LC 633 (two squares summing to c)
Missing Element in Sorted Array — LC 1060
Key point —
missing(i) = nums[i] − nums[0] − icounts the numbers absent before indexi, and it never decreases. Find the last index with fewer thankmissing; the answer is that many steps pastnums[i].
Reduces to: binary_search_on_answer § Template 1 — the first index with missing(i) >= k, over indices; the same derived-count trick as LC 1539 on that sheet.
# python
# LC 1060 - Missing Element in Sorted Array
# IDEA: missing(i) = nums[i] - nums[0] - i is monotone; find the first index with missing(i) >= k
# time = O(log n), space = O(1)
def missingElement(nums, k):
def missing(i):
return nums[i] - nums[0] - i # numbers absent between nums[0] and nums[i]
left, right = 0, len(nums) # right = n: the answer may lie past the last element
while left < right:
mid = left + (right - left) // 2
if missing(mid) >= k:
right = mid
else:
left = mid + 1
# left = first index with >= k missing, so index left - 1 has < k; the answer is past nums[left - 1]
return nums[left - 1] + k - missing(left - 1)
nums = [4,7,9,10], k = 3
missing: [0, 2, 3, 3]
first index with missing >= 3 -> 2
answer = nums[1] + 3 - missing(1) = 7 + 3 - 2 = 8 (missing numbers: 5, 6, 8)
Traps
- The k-th missing number can be past the array:
[4,7,9,10],k = 5→ 12.right = len(nums)covers it;right = len(nums) − 1does not. leftis never 0, becausemissing(0) = 0 < kfor everyk >= 1, soleft − 1is safe.
Similar — LC 1539 (missing positives counted from 1) · LC 275 (H-Index II: a derived count against the index)
Arranging Coins — LC 441
Key point —
kfull rows needk (k + 1) / 2coins, which grows withk. The answer is the largestkthat still fits, the lastTrueofTrue … True False … False.
Reduces to: binary_search_on_answer § Template 2 — largest feasible value with feasible(k) = k (k + 1) / 2 <= n, upper mid.
# python
# LC 441 - Arranging Coins
# IDEA: last k in [0, n] with k * (k + 1) // 2 <= n; upper mid so left = mid always moves
# time = O(log n), space = O(1)
def arrangeCoins(n):
left, right = 0, n
while left < right:
mid = left + (right - left + 1) // 2 # upper mid
if mid * (mid + 1) // 2 <= n:
left = mid # mid rows fit: the answer is mid or more
else:
right = mid - 1
return left
// java
// LC 441 - Arranging Coins
// IDEA: last k with k * (k + 1) / 2 <= n, in long; upper mid
// time = O(log n), space = O(1)
public int arrangeCoins(int n) {
long left = 0, right = n;
while (left < right) {
long mid = left + (right - left + 1) / 2;
if (mid * (mid + 1) / 2 <= n) left = mid;
else right = mid - 1;
}
return (int) left;
}
Traps
- The lower mid with
left = midloops forever onceright = left + 1:n = 1sticks atleft = 0, right = 1. mid * (mid + 1)overflowsintfornnear 2³¹: uselong.
Similar — LC 69 (the same “largest k whose cost fits”) · LC 2226 (largest feasible pile size)