- 二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
本来直接一次循环就可以解决,而且运行速度和内存消耗还优
但此题可以来加强二分查找的学习
题目说数组为有序数组,强调数组中无重复元素,一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些是使用二分法的前提条件
边界条件:
二分查找涉及的很多的边界条件
对区间的定义想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)
第一种:
定义target在[left, right]区间,所以有如下两点:
while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <= ;
if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
第二种:
while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的;
if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
第一种的代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
在计算中间位置时,如果直接使用int middle = (left + right) / 2,当left和right都非常大时,它们的和可能超出整数的表示范围,从而导致溢出错误。
而使用int middle = left + ((right - left) / 2)可以防止溢出,原因如下:
首先计算right - left:
这个差值通常会比left和right单独的值小很多,减少了数值过大导致溢出的风险。
即使left和right很大,但它们的差值可能在整数范围内。
然后将差值除以 2:
进一步减小了数值的大小,使得结果更不容易超出整数范围。
最后加上left:
这一步确定了中间位置相对于left的偏移量,确保得到的中间位置在left和right之间
第二种代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};