在演算法問題中,常常會遇到需要找到一個最優解的情況,例如最大值、最小值等,此時我們可以考慮使用二分搜來加速求解。二分搜不僅可以應用於有序數組中尋找目標值,也可以推廣到求解極值問題。
什麼是極值問題?#
極值問題指的是在一個定義域內尋找最大值或最小值的問題。通常我們可以將問題簡化為一個目標函式 f(x),需要在 x 的定義域內找到 f(x) 的最大值或最小值。
二分搜解決極值問題#
對於一個單調函式f(x),我們可以使用二分搜來尋找極值。具體步驟如下:
- 確定目標函式
f(x)的定義域,設為[l, r]。 - 在定義域內二分取中點
mid = (l + r) / 2。 - 計算
f(mid)的值,並根據單調性判斷極值是在[l, mid]還是[mid + 1, r]中。 - 更新搜索區間,繼續二分搜。
- 重複步驟 2-4 直至搜索區間足夠小,得到近似極值。通常如果搜索區間恆為整數,二分搜終止條件是
r - l = 1。
假設現在有一個左邊遞增,右邊遞減的陣列或vector,你可以假設v[x]就是剛剛說的f(x),對他二分搜就可以得到極值,範例程式碼如下:
#include <bits/stdc++.h>
using namespace std;
int findPeakElement(vector<int>& nums) {
int l = 0, r = nums.size(); // l 跟 r 永遠不會被搜到,除非只有一個元素
while (r - l != 1) { // 如果遇到r - l = 1沒有停下來,就會無窮迴圈(想一想,為什麼)
int mid = (l + r) / 2;
if (nums[mid] < nums[mid + 1]) { // 左半是單調遞增,右邊是單調遞減
// 極值在右半段
l = mid + 1;
} else {
// 極值在左半段或mid處
r = mid;
}
}
// 最終l和r重合在極值點
return r;
}
int main() {
vector<int> nums = {1, 3, 5, 7, 9, 8, 6, 4, 2};
int peak = findPeakElement(nums);
cout << "Peak element is: " << nums[peak] << endl;
return 0;
}
輸出:
Peak element is: 9
在上述程式碼中:
- 我們定義了一個函式
findPeakElement。 - 在函式內部,我們使用變量
l和r分別表示數組的左右邊界。 - 我們使用一個
while迴圈進行二分搜,每次比較中點元素nums[mid]與其左右鄰居的大小關係。根據比較結果,我們更新邊界l或r,繼續二分搜。 - 迴圈退出時,
l和r重合在極值點上。我們返回r作為極值點的索引。 - 在
main函式中,我們定義了一個單峰數組nums,並調用findPeakElement函式找到極值點的索引。最後我們輸出該極值點的值。
當然,你可以真的搜一個單峰函式,好比說你熟知的二次函式之類的。不過有時候二分搜會動用浮點數,所以要注意之前提到過的精度問題。
