算法专题--数组二分查找--Leetcode704题

储备知识

学习本算法需要掌握:while-else if -else语句,二分法,循环语句

重难点

边界处理(到底是边界还是边界+-1?到底是大于小于还是大于等于、小于等于?)

题目704

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target ,如果目标值存在返回下标,否则返回 -1 。

题目解读

以上题目的意思就是说,现在有一个数组,要求你找出目标值,返回目标值的下标,此题数组二分查找的方法。

我们首先把最左边的元素叫left,最右边的叫做right。最中间的元素叫middle。

那么我们有一下两种二分查找的写法

注意:一个字一个字看,边看边想,不要走神

1.左闭右闭法

顾名思义,我们在区间【left,right】中搜寻目标值target。我们可以认为[left,right]这个升序数组的区间是左闭右闭的。至于两端都是闭区间意味着什么,请你看下去就会理解。

我们使用"二分查找法"。这种方法的核心就是不断的二分来逼近目标值,就像高中所学的求函数交点的二分法一样。不过在数组中,我们相当于是在数轴上离散的点进行操作而已。

我们先对得到的这个区间求中间值,然后拿中间值和目标值比较。这时出现3种情况:

1.最中间的一个数的值 = 目标值。此时就找到了目标值。

2.如果位于数组中间的值 > 目标值,说明目标值在左侧区间;

3.如果中间值 < 目标值,说明目标值在右侧区间;

此时再对目标值所在的区间再次进行二分,再次判断它在哪个区间。不断重复上述操作,直到中间值等于目标值,此时我们就可以获得这个中间值的下标。返回该下标就解决了这个问题。

那么我们可以大概写一下这个函数的结构

当左边界left小于等于右边界right时 while(left<right){

//Q1:为什么是小于等于?详情见下文蓝字部分

中间下标middle=(左下标left+右下标right)/2

if(中间下标对应的数值nums[middle] < 目标值target){

//说明目标值在右侧区间,此时把左下标更新为中间值,不去理会左半部分和中间值

left=middle-1 // Q2:为什么要 -1?

else if(nums[middle] > target)

right=middle-1 //Q2

else return middle; //中间值等于目标值直接返回中间的下标

}

return -1

//没找到目标值返回-1,题目要求的

}

解释Q1Q2

Q1:为什么是小于等于?

因为我们的区间是左闭右闭的,拿数学来解释,假设有一个数x属于区间【a,b】那么 可以得到 a<=x<=b,这个区间里面x=a=b是完全合法的。方括号闭区间就代表等于和包含。

我们在程序中为了严谨,在左闭右闭的大前提下,当然要用<=确保我们每一个数都参与了运算。

如果你还是一头雾水。那么我们反过来说,如果在上面的while条件中不用=,那么当left==right的时候程序就不会继续执行这下面的判断条件if语句,我们就无法知道这最后一个数(left=right=middle)是否和目标值target相等。

Q2:为什么要+/-1?

我们在程序中if的判断条件是">"和"<",else middle=target则直接返回下标

这就意味着前两个大于小于语句中的middle已经判断过是和target不相等的了,所以我们在下一轮的判断中就不需要第一轮的middle参与了,所以直接+/-1从下一个开始算起

左闭右闭法代码展示

cpp 复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left=0;
        int right=nums.size()-1 ; //计算机里下标都是从0记起的,数组长度-1才是下标
//换种说法我们要求的是左闭右闭,所以right一定得是个有效下标
        while(left<=right){
            int middle=left+(right-left)/2;  //防止溢出,超大数字直接相加除2可能超过INT_MAX(如32位最多只能存2^32,超出会变负数)
            if(nums[middle]<target){
                left=middle+1;  // 注意区分+/-1,防止边界反而扩大导致超时
            }
            else if(nums[middle]>target){
                right=middle-1;  // 注意区分+/-1,防止边界反而扩大导致超时
            }
            else{
                return middle;
            }
            
        }
        return -1;  //注意不能在while里面
    }
};

代码要点

1.int middle=left+(right-left)/2;

在计算中间下标时,通常写作:

int middle = (left + right) / 2;

但这样在 leftright 都很大时(比如接近 INT_MAX,即 2147483647),left + right 可能超过 int 能表示的最大值,造成整数溢出。溢出后结果会变成负数(或未定义值),用它做下标访问数组必然越界,程序可能崩溃或出现奇怪行为。

例如,假设 left = 2000000000, right = 2000000000,那么 left+right = 4000000000,超过 INT_MAX,在 32 位 int 中实际会变成 4000000000 - 2^32 ≈ -294967296,除以 2 得到负数,显然不是正确的中间位置。

所以求差值得长度是最保险的方法。

2.左闭右开法

左闭右开的条件下,我们参考上面Q1的思路就可以很明显的知道,在 while 句中,就不能再使用"="了,就和数学上的定义一样,假设x是在区间 [a,b) 中的一个数,那么可得a<=x<b,显然a/b两个端点/边界是不存在相等的可能性的。

主体思路一样,我们直接说和上面方法里的不同点。

代码要点/不同点

1.初始化: left = 0, right = nums.size()(因为 right 是开区间,所以它等于数组长度,就是无效下标,最后一个有效下标是 right-1,因为我们要的是左开右闭,就是要让左右不相等

  1. while (left < right)(当 left == right 时区间为空,无元素可查)

3.边界更新:

  • nums[middle] < targetleft = middle + 1(目标在右边,且 middle 已比较,应排除,新区间为 [middle+1, right)

  • nums[middle] > target ⇒ right = middle(目标在左边,新区间为 [left, middle),注意 middle 本来就不包含在内,这个区间在while那一步就已经决定了)

cpp 复制代码
class Solution {
public:
   
        int search(vector<int>& nums, int target) {
    int left = 0, right = nums.size();  // 注意 right 初始为数组长度
    while (left < right) {               // 区间非空
        int middle = left + (right - left) / 2;
        if (nums[middle] < target) {
            left = middle + 1;           // 目标在右边,移动左边界
        } else if (nums[middle] > target) {
            right = middle;               // 目标在左边,移动右边界(开区间)
        } else {
            return middle;                 // 找到
        }
    }
    return -1;  // 未找到
}
   
};
相关推荐
北寻北爱2 小时前
axios
开发语言·前端·javascript
biter down2 小时前
C++ stringstream 简单介绍:告别字符数组,安全高效的字符串与数据转换利器
开发语言·c++
C+-C资深大佬2 小时前
C++ 模板进阶
开发语言·c++·算法
菜_小_白2 小时前
高并发定时任务调度系统
linux·c++
耶叶2 小时前
C++:拷贝构造函数
开发语言·c++
努力中的编程者2 小时前
栈和队列(C语言底层实现栈)
c语言·开发语言·数据结构·c++
SunnyDays10112 小时前
使用 Python 轻松操控 Excel 网格线:隐藏、显示与自定义颜色
开发语言·python·excel
无尽的罚坐人生2 小时前
hot 100 98. 验证二叉搜索树
算法·leetcode
落叶@Henry2 小时前
C# async 和await 的面试题
开发语言·c#