【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) return mid; 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; } };