写二分查找不得不注意的细节!!!说三遍

Problem: 367. 有效的完全平方数
problem: 704.二分查找
  1. 思路

  1. 二分查找的整体思路

  1. (一)区间问题 right取值

  1. (二)left right移动的位置

  1. (三)循环条件

  1. 解题方法

  1. 复杂度

  1. Code

思路

二分查找的整体思路

二分查找模板(左闭右闭)
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) >> 1);
        if nums[middle] == target{
            return middle; // 数组中找到目标值,直接返回下标
        }else if(nums[middle] > target) {
            right = middle-1; // target 在左区间,在[left, middle]中
        } else if (nums[middle] < target) {
            left = middle + 1; // target 在右区间,在[middle + 1, right]中
        }
    }
    // 未找到目标值
    return -1;
}

二分查找模板(左闭右开)
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{
            return middle; // 数组中找到目标值,直接返回下标
        }else if(nums[middle] > target) {
            right = middle; // target 在左区间,在[left, middle)中
        } else if (nums[middle] < target) {
            left = middle + 1; // target 在右区间,在[middle + 1, right)中
        }
    }
    // 未找到目标值
    return -1;
}

二分查找思路不难,就是在有序无重复元素中,通过一个left和一个right,确定一个middle,从而将数据一分为二

再将midddle与 目标值target进行比较。

  1. 如果middle等于target ,显然我们要就返回对应middle下标。

  1. 如果middle大于target ,显然我们要将left右移。(注意!我们这里的数据是升序的!!!)

  1. 如果middle小于于target ,显然我们要将left左移。

再更新middle值,在目标值所在区间继续进行二分查找,不断循环下去

这里主要的是一些细节大家容易出错。

  1. 循环的条件
    在while( left ? right ) 里面用 > 还是 >= 呢?

  1. left right移动的位置
    if( 表达式 ){ right = ? } 里面是middle 还是 midlle-1/middle-1 呢?

要想解决这样的细节问题,最重要的就是先解决区间问题

(一)区间问题 right取值

一般常用的就是两种区间

  1. 左闭右闭 [left,right]

  1. 左闭右开 [left,right)

在区间中,想要查找到每个元素,就要先想想自己用的是 左闭右闭 还是 左闭右开

其实不管是左闭右闭还是左闭右开,都需要明确,我们要查询过每一个要查元素。

开始,我们比较的是始末元素和中值。这里就需要注意哪个right=nums.size()-1哪个是nums.size()

左闭右闭

如果是左闭右闭,由于左右边界我们都可以取到,我们的leftright开始就应该在数组的始末。所以right=nums.size()-1

左闭右开

如果是左闭右闭,由于右边界我们取不到,我们的right开始应该在数组的末尾的后一位。所以right=nums.size()

(二)left right移动的位置

解决了开始的 right取值,下面解释left right移动的位置就方便多了

还是刚才的图 , 由于每一次middle都会被查找过, 所以我们下次的查找的范围都应该是从leftmiddle-1或者middle+1right

因为开区间right取不到自己,所以范围相当于从middle+1right-1,而我们之前初始化的时候已经给 right加过1了,所以范围也是middle+1right

左闭右闭

由于是左闭右闭,左右边界leftright我们都可以取到,所以找到目标区间后,我们的leftright应该跳过middle, 从middle-1或者middle+1开始查找,即right=nums.size()-1 / left = middle+1

左闭右开

由于是左闭右闭,右边界right我们不可以取到,所以找到目标区间后,我们的left应该跳过middle,即left = middle+1 ,而right应该等于middle,从而使查找的范围,也是从leftmiddle-1,middle+1right,即right=middle / left = middle+1

(三)循环条件

知道了left right移动的位置,然后就该讨论下一个细节问题,循环条件。

在之前都没有找到目标值的情况下,最后一次循环结束的条件应该是,我们把目标区间里的数,也就是最后一次的leftright里的数都应该查询了一遍。

左闭右闭

(本例为假设)

如果target = 5,上次left = 4 right = 5 middle = 4

由于nums[middle] < target,left = middle+1,所以left = right = 5

这时left = right,我们该不该继续循环呢?

答案时肯定的,因为我们还没有找到5,意味着middle还没有取到5,所以我们应该继续循环让middle=5.

此时,middle刚好时5,结束返回下标结束。

但是,如果这时不是5,我们又取完了区间内所有的数,下一次就会结束循环,也就是left>right时,结束循环,所以循环条件是left<=right

(还有一种情况就是left右移,也会结束循环)

左闭右开

(本例为假设)

如果target = 5,上次left = 5 right = 7(下标) middle = 6(下标)

由于nums[middle] < target,right = middle

这时middle = left,由于我们没有取过left对应元素,所以循环应该继续。

再循环一次,由于nums[middle] < target,right = middle,所以 left = right = middle = 5

由于这时已经取过了所有区间内元素,所以left = right时就应该结束循环,所以循环的条件是left<right

解题方法

掌握了 二分查找就好解决了
  1. 区间选择左边右闭。

  1. 循环条件left <= right

  1. left = middle+1 , right =middle-1

只是把比较的middle 变成了 middle*middle,要注意类型转换,防止数据溢出。

复杂度

  • 时间复杂度:

O(logn)O(logn) O( logn)

Code

C++


class Solution {
public:
    bool isPerfectSquare(int num) {
        int left = 0, right = num;
        while(left <= right){
            int middle = left + (right - left)/2;
            long square = (long)middle * middle; //要强制类型转换为 long 类型
            if( square == num){
                return true;     
            }else if(square > num){
                right = middle - 1;
            }else if(square < num){
                left = middle + 1;
            }
        }

            return false;
        
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JAVA技术开发员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值