题目描述
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
注意:
- 你可以假设 nums 中的所有元素是不重复的。
- n 将在 [1, 10000]之间。
- nums 的每个元素都将在 [-9999, 9999]之间。
题目链接: https://leetcode-cn.com/problems/binary-search/
思路
经典的二分查找问题。二分查找的难点在于不容易写对,也就是不容易判断应该使用 < 还是 <=,还有要不要 +1,-1 的问题。二分查找主要有两种写法:
写法一
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.empty()) return -1;
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){
right = mid-1;
}
else if(nums[mid]<target){
left = mid+1;
}
}
return -1;
}
};
写法二
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.empty()) return -1;
int left = 0, right = nums.size();
while(left<right){
int mid = left + (right-left)/2; // 防止溢出
if(nums[mid]==target) return mid;
else if(nums[mid]>target){
right = mid;
}
else if(nums[mid]<target){
left = mid+1;
}
}
return -1;
}
};
两种写法的不同体现在3个方面,如下图:
3处不同分别是:
- right的初始化;
- while循环条件;
- right的更新方式;
3处不同的原因取决于我们的搜索范围是[left, right]还是[left, right)。下面分析两种不同写法的区别以及原因:
-
对于写法1,right初始化为nums.size()-1,意味着搜索的范围为[left, right],因为nums.size()-1指向nums的最后一个元素,如果搜索范围是[left, right)的话,最后一个数字就不在搜索范围了。在搜索范围为[left, right]的情况下,left==right也就是[left, left]这样的范围是有意义的,例如[2,2]意味着对下标为2的数字进行搜索,所以while的条件为left<=right。在搜索的过程中,我们根据mid指向的元素与target之间的大小关系来选择接下来的搜索范围,因为搜索范围是[left, right],所以在更新时right要更新为mid-1,如果不减一的话,下一步搜索的范围就包含mid了。
-
对于写法2,right初始化为nums.size(),意味着搜索范围为[left, right),因为right初始指向num.size(),也就是nums最后一个数字的下一个位置,[left, right)不包含right指向的元素,刚好搜索范围是整个数组。在搜索范围是[left, right)的情况下,left==right也就是[left, left)这样的条件是无意义的,所以while的条件为left<right。因为搜索范围是左闭右开,不包含right指向的元素,所以right=mid即可,无需更新为mid-1,因为mid不会被包含进搜索范围中。