【Leetcode】34. 在排序陣列中查詢元素的第一個和最後一個位置 Medium
本題並不困難, 但是引發了我對二分查詢演算法的一些思考。
二分查詢演算法的實現思想有兩種:
思路 1:在迴圈體中查詢元素
迴圈內部有三個分支:
int search(vector<int>& nums, int target) { int left = 0, right = nums.size() - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) returnmid; else if (nums[mid] < target) left = mid + 1; else right = mid - 1; } return -1; }
思路 2:在迴圈體中排除目標元素一定不存在的區間
迴圈內部有兩個分支:
int search(vector<int>& nums, int target) { int left = 0, right = nums.size() - 1; while (left < right) {int mid = left + (right - left) / 2; if (nums[mid] < target) left = mid + 1; else right = mid; } if (nums[left] == target) return left; return -1; }
int search(vector<int>& nums, int target) { int left = 0, right = nums.size() - 1; while (left < right) { int mid = left + (right - left + 1)/2; if (nums[mid] > target) right = mid - 1; else left = mid; } if (nums[left] == target) return left; return -1; }
排除法這裡有很多需要思考, 比如迴圈終止條件、mid需不需要向上取整。 總結一下編碼要點編碼要點
迴圈終止條件寫成:while (left < right) ,表示退出迴圈的時候只剩下一個元素;
在迴圈體內考慮如何縮減待搜尋區間,也可以認為是在待搜尋區間裡排除一定不存在目標元素的區間;
根據中間數被分到左邊和右邊區間,來調整取中間數的行為;
如何縮小待搜尋區間,一個有效的辦法是:從 nums[mid] 滿足什麼條件的時候一定不是目標元素去考慮,進而考慮 mid 的左邊元素和右邊元素哪一邊可能存在目標元素。一個結論是:當看到 left = mid 的時候,取中間數需要上取整,這一點是為了避免死迴圈;
退出迴圈的時候,根據題意看是否需要單獨判斷最後剩下的那個數是不是目標元素。
邊界設定的兩種寫法:
right = mid 和 left = mid + 1 和 int mid = left + (right - left) / 2; 一定是配對出現的;
right = mid - 1 和 left = mid 和 int mid = left + (right - left + 1) / 2; 一定是配對出現的。
這一點不需要記憶,只要一直思考目標元素可能存在的區間就可以了。因為下一輪搜尋的區間是 [left, mid] 所以這個時候設定 right = mid 。當前這條性質不滿足的時候,既然整個區間是 [left, right] 區間裡,第一種情況所在區間是 [left, mid] ,那麼另外一種情況對應的區間是 [mid + 1, right] ,兩個區間合起來就是整個區間 [left, right] 。同理,去理解 right = mid - 1 和 left = mid 這兩個邊界設定。
回到原題, 需要使用排除法找出target值的上下邊界
class Solution { public: vector<int> searchRange(vector<int>& nums, int target) { if (!nums.size()) return vector<int>{-1, -1}; return vector<int> {searchLowerBound(nums, target), searchUpperBound(nums, target)}; } int searchLowerBound(vector<int>& nums, int target) { int left = 0, right = nums.size() - 1; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] < target) left = mid + 1; else right = mid; } if (nums[left] != target) return -1; return left; } int searchUpperBound(vector<int>& nums, int target) { int left = 0, right = nums.size() - 1; while (left < right) { int mid = left + (right - left + 1) / 2; if (nums[mid] > target) right = mid - 1; else left = mid; } if (nums[right] != target) return -1; return right; } };