二分搜尋 — 實戰題解
範圍 — binary_search.md 的題組:每題歸檔在它所歸約的索引空間模板底下,寫成一張題目卡、一份標準解,另外收錄少數沒在 binary_search_on_answer.md 解過的答案空間題。 另見 — 主文件:binary_search.md — 迴圈不變式、邊界模板,以及「該用哪個模板」的決策表;binary_search_on_answer.md — 所有對答案而非索引做二分搜尋的題目。 鄰近文件:n_sum.md — LC 167 的收斂指標解法;sort.md — LC 354 的先排序再 LIS 解法;patience_sorting.md — 歸約到 LIS 的題目;matrix.md — 二維網格題型家族。
LeetCode 題目清單
概覽
binary_search.md 收著八個索引空間模板;這一頁是題目。下面每一節對應一個模板, 順序與主文件相同,每張卡片只說明它比模板多出的那一步。錨定題本身就是全部重點的模板 (模板 4、6、8)在這裡沒有獨立一節。最後一節收錄歸約到 binary_search_on_answer.md 卡片的答案空間題,指標表則列出在其他文件解過的題目。
索引
在別處解 — 只解一次,放在擁有它的那份文件:
| LC | 題目 | 所在位置 | 為什麼在那裡 |
|---|---|---|---|
| 162 | Find Peak Element | binary_search.md § Template 7 | 該模板的錨定題 |
| 300 | Longest Increasing Subsequence | binary_search.md § Template 5 | 該模板的錨定題 |
| 354 | Russian Doll Envelopes | sort.md § Template 9 | 重點是帶平手規則的排序;之後的 LIS 就是模板 5 |
| 167 | Two Sum II — Input Array Is Sorted | n_sum.md | 雙指標是 O(n);每個元素各做一次二分搜尋是 O(n log n) |
| 209 | Minimum Size Subarray Sum | sliding_window.md § Template 4 | 滑動視窗是 O(n);前綴和 + 每個起點一次 lower bound 是 O(n log n) 的追問 |
| 69 | Sqrt(x) | math.md § Integer Square Root | 對答案二分搜尋與牛頓法並排 |
| 528 | Random Pick with Weight | math.md § Randomized Sampling | 前綴和 + 對均勻抽樣做 lower bound |
| 981 | Time Based Key-Value Store | design_examples.md | 每個 key 一份排序清單,用 upper bound − 1 查詢 |
| 287 | Find the Duplicate Number | binary_search_on_answer.md § Find the Duplicate Number | 在值域上二分搜尋,配合鴿籠計數 |
精確比對範例
Median of Two Sorted Arrays — LC 4 優先度 5/5 — 必備 — 幾乎每一輪面試都會出現
關鍵 — 搜尋的是較短陣列裡的切點,不是某個值。
nums1的切點i決定了nums2的切點j = half − i;當兩個左側最大值都<=兩個右側最小值時切點就對了,切錯時,看是哪個不等式 不成立就知道該往哪邊移。
歸約到:binary_search § Template 1 — 同一個閉區間迴圈、三種結果(找到、往左、往右),只是搜尋的是切點位置 [0, m] 而不是索引,判斷用跨陣列的比較而不是 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;
}
陷阱
- 搜尋較長的陣列會讓
j = half − i變成負數:nums1 = [1, 2, 3, 4]、nums2 = [5]可能探測到i = 4,於是j = −1。先交換,讓nums1是較短的那個。 - 空的一邊需要
±inf哨兵:nums1 = []、nums2 = [1]時i = 0,沒有nums1[i − 1]。 - Java 中兩個大 int 的
max + min會在除法前溢位:以double相加。
相似題 — LC 2040 (第 k 小的乘積:binary_search_on_answer § Template 4)
下界與上界範例
First Bad Version — LC 278
關鍵 —
isBadVersion讀起來是False … False True … True,所以第一個壞版本就是模板 2 的findLeft,比較換成呼叫 API。沒有陣列,也沒有 target。
歸約到:binary_search § Template 2 — 把 nums[mid] >= target 換成 isBadVersion(mid) 的 findLeft;答案一定存在,所以不用驗證。
# 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;
}
陷阱
- Java 中
(left + right) / 2會溢位:n = 2126753390就是 LeetCode 自己的測資。
相似題 — LC 374 (猜數字 API,精確比對) · LC 35 (插入位置)
Find Smallest Letter Greater Than Target — LC 744
關鍵 — 「嚴格大於 target 的最小字母」是 upper bound(第一個
> target的字母)。 沒有更大的字母時答案繞回letters[0],% n直接處理掉。
歸約到:binary_search § Template 2 — 在字元上做 findRight + 1(upper bound),再把索引繞回。
# 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]
陷阱
- 用
letters[mid] < target找到的是第一個>= target的字母:letters = ["c","f","j"]、target = "c"會回傳"c"而不是"f"。 - 允許重複:
["e","e","e","n"]、target = "e"必須跳過每個"e",<=就能做到。
Check If a Number Is Majority Element in a Sorted Array — LC 1150
關鍵 — 在排序陣列裡,同一個值佔據一段連續區間。找出
target那段的起點; 它是多數元素,恰好當往後n // 2個位置的元素仍是target。
歸約到:binary_search § Template 2 — 一次 findLeft,接著一次 O(1) 檢查,取代第二次搜尋。
# 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
陷阱
- 漏掉
end < n的檢查:nums = [1, 2]、target = 2起點是 1,1 + 1 = 2已經越界。 - target 不存在時:lower bound 落在別的值上,檢查仍回傳
False,不需要另外判斷「有沒有找到」。
相似題 — LC 34 (區間的兩端) · LC 169 (未排序的多數元素:Boyer–Moore)
Single Element in a Sorted Array — LC 540
關鍵 — 在單一元素之前,每一對都從偶數索引開始;在它之後則從奇數索引開始。所以 「偶數
mid那一對完整」是True … True False … False,單一元素就是第一個不成立的偶數索引。
歸約到:binary_search § Template 2 — 對奇偶判定式做 lower bound,只探測偶數索引。
# 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];
}
陷阱
- 不把
mid對齊到偶數,nums[mid] == nums[mid + 1]可能比到一對的後半:[1,1,2,3,3,4,4]探測mid = 3,看到3 == 3,跳到left = 5,回傳 4 而不是 2。 mid + 1一定在範圍內,因為left < right且right永遠是偶數(長度是奇數)。
相似題 — LC 136 (未排序:O(n) 的 XOR)
Find K Closest Elements — LC 658
關鍵 — 答案是一個視窗
arr[i .. i + k − 1],所以搜尋視窗的起點。比較平移時會互換的 兩個元素:若x − arr[i] > arr[i + k] − x,從i + 1開始的視窗至少一樣近,而這個判斷只會從True轉成False一次。
歸約到:binary_search § Template 2 — 在視窗起點 [0, n − k] 上做 lower bound,判定式是「往右移不會更好」。
# 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]
陷阱
- 用
abs(x - arr[mid]) > abs(arr[mid + k] - x),在兩者都在x同一側時會出錯:arr = [1,1,2,2,2,2,2,3,3]、k = 3、x = 3回傳[2,2,2]而不是[2,3,3]。保留帶正負號的差值。 - 用
>=取代>會在平手時偏向較大的值;題目要較小的:arr = [1,2,3,4,5]、k = 4、x = 3必須回傳[1,2,3,4]。
第二種做法 — 從兩端往內縮(丟掉比較遠的那端)直到剩 k 個:O(n − k),想不出二分判定式時
就給這個版本。依距離排序(sort.md)是 O(n log n)。
Find Right Interval — LC 436 優先度 4/5 — 高價值 — 這裡有缺口就會掉關
關鍵 — 對每個區間,答案是所有區間中最小的
start >= end_i:在排序後的 start 上做 lower bound。答案必須是原始索引,所以排序(start, index)配對,再從搜尋落點的配對讀出索引。
歸約到:binary_search § Template 2 — 每個區間一次 findLeft,在連同原始索引一起排序的 start 上搜尋。
# 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
陷阱
- 直接排序區間本身會弄丟答案需要的索引:在排序之前把每個 start 和它的索引配成一對。
- 區間可以是自己的右區間:
[[1,1]]回傳[0](start1 >= end 1)。 - 雙層迴圈是 O(n²),在 n = 2·10⁴ 時會超時;重點就是每個區間只做一次 O(log n) 搜尋。
相似題 — LC 35 (最單純的 lower bound) · LC 2300 (每個 spell 一次 lower bound,數後綴) · LC 1268 (對逐步變長的前綴做 lower bound)
旋轉陣列最小值範例
Find Minimum in Rotated Sorted Array II — LC 154
關鍵 — 重複值帶來第三種情況:
nums[mid] == nums[right]無法判斷mid在哪一段,但此時nums[right]不是唯一的最小值(nums[mid]等於它),所以right -= 1丟掉它是安全的。 最壞情況變成 O(n)。
歸約到:binary_search § Template 3 — 同樣是對 nums[right] 的半開擠壓,相等時多一步縮一格。
# 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];
}
陷阱
- 改成和
nums[left]比較:[1, 3, 3]沒有旋轉,nums[mid] >= nums[left]把搜尋送往右邊, 錯過索引 0 的1。 - O(n) 最壞情況是真的:
[1,1,1,1,0,1,1]任何探測都無法判斷。被問到時要說出來。
相似題 — LC 81 (含重複值的搜尋:在模板 4 上用同樣的 right -= 1 退路)
找峰值範例
Peak Index in a Mountain Array — LC 852
關鍵 — 山形陣列恰好只有一個峰,所以模板 7 不用改:往
mid較高的鄰居走,直到視窗只剩一個索引。
歸約到:binary_search § Template 7 — 原封不動;山形保證讓峰值唯一。
# 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
相似題 — LC 162 (任意峰值,沒有山形保證) · LC 1095 (見下) · LC 1901 (二維峰值)
Find in Mountain Array — LC 1095 優先度 5/5 — 必備 — 幾乎每一輪面試都會出現
關鍵 — 山形陣列是兩段排序區間在峰頂接起來。用模板 7 找出峰值,接著先搜尋遞增的左段 (索引最小的勝出),之後才搜尋遞減的右段,比較方向要翻轉。三次 O(log n) 搜尋能維持在
get()的 100 次呼叫上限之內。
歸約到:binary_search § Template 7 — 先找峰值,再做兩次模板 1:左段照原樣,右段把方向翻轉。
# 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)
陷阱
- 先搜右段會回傳較大的索引:
[1,2,3,4,5,3,1]、target = 3必須是 2,不是 5。 - 在右段沿用遞增的比較會走錯方向:
[0,5,3,1]、target = 1永遠找不到。 - 線性掃描找峰值在 n = 10⁴ 時會超出呼叫額度。
相似題 — LC 852 (只找峰值) · LC 33 (被旋轉切開的兩段排序區間:binary_search § Template 4)
對答案搜尋範例
Valid Perfect Square — LC 367
關鍵 — 搜尋的是根,不是陣列:
[1, num]上第一個讓r * r >= num的r,就是單調判定式的 第一個True。num是完全平方數,恰好當那個r的平方等於它。
歸約到:binary_search_on_answer § Template 1 — 用 feasible(r) = r * r >= num 找最小可行值,再做一次相等檢查。
# 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;
}
陷阱
- Java 中
int的mid * mid在mid > 46340時溢位:num = 2147483647會探測到mid ≈ 10⁹。用long。 - 題目禁止用函式庫的
sqrt;牛頓迭代是另一個被接受的答案 (math.md § Integer Square Root)。
相似題 — LC 69 (下取整的根:最後一個 r * r <= x 的 r) · LC 633 (兩個平方數之和為 c)
Missing Element in Sorted Array — LC 1060
關鍵 —
missing(i) = nums[i] − nums[0] − i計算索引i之前缺了幾個數,而且絕不會變小。 找出最後一個缺數少於k的索引;答案就是從nums[i]再往後數那麼多步。
歸約到:binary_search_on_answer § Template 1 — 在索引上找第一個 missing(i) >= k;和那份文件裡的 LC 1539 是同一個推導計數技巧。
# 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)
陷阱
- 第 k 個缺數可能落在陣列之後:
[4,7,9,10]、k = 5→ 12。right = len(nums)涵蓋得到;right = len(nums) − 1則不行。 left永遠不會是 0,因為對每個k >= 1都有missing(0) = 0 < k,所以left − 1是安全的。
相似題 — LC 1539 (從 1 起算的缺失正整數) · LC 275 (H-Index II:推導出的計數對上索引)
Arranging Coins — LC 441
關鍵 —
k個完整的列需要k (k + 1) / 2枚硬幣,隨k遞增。答案是仍放得下的最大k, 也就是True … True False … False的最後一個True。
歸約到:binary_search_on_answer § Template 2 — 用 feasible(k) = k (k + 1) / 2 <= n 找最大可行值,偏上的 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;
}
陷阱
- 偏下的 mid 搭配
left = mid,一旦right = left + 1就永遠迴圈:n = 1會卡在left = 0, right = 1。 mid * (mid + 1)在n接近 2³¹ 時溢位int:用long。
相似題 — LC 69 (同樣是「成本放得下的最大 k」) · LC 2226 (最大可行的每堆數量)