代码随想录学习记录——数组篇-二分查找

704、二分查找

重点是如何选择区间的定义,这关系到我们的更新逻辑及判断条件。

1、定义左闭右闭区间

因为是左右都可以取到取值,那么 l e f t = = r i g h t left == right left==right是有意义的,那么循环的判断条件则为 l e f t < = r i g h t left <= right left<=right

其次,当 n u m s [ m i d d l e ] ! = t a r g e t nums[middle] != target nums[middle]!=target时,要 l e f t = m i d d l e + 1 left =middle + 1 left=middle+1或者是 r i g h t = m i d d l e − 1 right =middle- 1 right=middle1,因为当前这个 m i d d l e middle middle的取值已经不是 t a r g e t target target,并且我们的区间是左右都闭合,即左右都可以取到的,那么这个 m i d d l e middle middle再取值就没意义了,那么就可以舍弃掉当前这个 m i d d l e middle middle

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size()-1;
        while(left <= right){
            int middle = (left + right) / 2;
            if (nums[middle] < target){
                left = middle + 1;
            }else if (nums[middle] > target){
                right = middle - 1;
            }else{
                return middle;
            }
        }
        return -1;
    }
};

2、定义为左闭右开区间

定义区间为: [ l e f t , r i g h t ) [left,right) [left,right),那么 r i g h t right right这个我们是一直取不到取值的,因此循环的判断条件为 l e f t < r i g h t left < right left<right,等于已无意义;其次在更新时也要按照闭合和开的区间边界来区分开,因此右边界 r i g h t right right一直不会取到,那么不能取 m i d d l e − 1 middle-1 middle1否则就会忽略掉 m i d d l e − 1 middle-1 middle1的判断。
因此更新公式为 r i g h t = m i d d l e right = middle right=middle 以及 l e f t = m i d d l e + 1 left = middle + 1 left=middle+1

其次,初始化的时候由于右边界取不到,因此 r i g h t = n u m s . s i z e ( ) − 1 right = nums.size() - 1 right=nums.size()1的话会将结尾元素忽略掉,因此取 n u m s . s i z e ( ) nums.size() nums.size()

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size(); #注意这里不再是size()-1,因此这个边界我们取不到
        while(left < right){
            int middle = (left + right) / 2; 
            if (nums[middle] < target){
                left = middle + 1;
            }else if (nums[middle] > target){
                right = middle;
            }else{
                return middle;
            }
        }
        return -1;
    }
};

能不能定义为左开右闭?

不行!因为这样定义的话 在初始化的时候 l e f t left left就不能等于0,因为这样会由于区间左边是开的,取不到左边界的值,那么就忽略掉 n u m s [ 0 ] nums[0] nums[0]的值。而再往左边走就是 − 1 -1 1了,整个后面的逻辑就乱了,因此不可以。

35、搜索插入位置

本题跟二分查找类似,增加一个找不到如何返回插入的索引即可

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int middle;
        while (left <= right){
            middle = ( left + right ) / 2;
            if (nums[middle] < target){
                left = middle+1;
            }else if(nums[middle] > target){
                right = middle-1;
            }else{
                return middle;
            }
        }
        if (nums[middle] > target){
            return middle;
        }else{
            return middle + 1;
        }
    }
};

在我的代码中找不到后我进行了大小的判断,从而解决了插入位置索引的问题。而代码随想录中的解决方案更是灵活

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int middle;
        while (left <= right){
            middle = ( left + right ) / 2;
            if (nums[middle] < target){
                left = middle+1;
            }else if(nums[middle] > target){
                right = middle-1;
            }else{
                return middle;
            }
        }
		return right + 1
    }
};

因为插入到其中只可能有四种情况:

  • 插入到所有元素的左边:即 t a r g e t target target比所有元素都小,那么在判断的时候就是 r i g h t right right不断向 l e f t left left靠近,直接 r i g h t = = l e f t = = 0 right == left == 0 right==left==0的时候仍然是 n u m s [ m i d d l e ] > t a r g e t nums[middle] > target nums[middle]>target 因此就 r i g h t = m i d d l e − 1 right = middle - 1 right=middle1 ,那么此时 r i g h t = = − 1 right == -1 right==1,而要返回的位置为第一个位置即索引0,因此返回 r i g h t + 1 right + 1 right+1
  • 插入到所有元素的右边:即 t a r g e t target target比所有元素都大,那么在判断的时候就是 l e f t left left不断向 r i g h t right right靠近,直接 r i g h t = = l e f t = = n u m s . s i z e ( ) − 1 right == left == nums.size()-1 right==left==nums.size()1的时候仍然是 n u m s [ m i d d l e ] < t a r g e t nums[middle] < target nums[middle]<target 因此就 l e f t = m i d d l e + 1 left = middle + 1 left=middle+1 ,那么此时 l e f t = n u m s . s i z e ( ) , r i g h t = n u m s . s i z e ( ) − 1 left = nums.size(),right = nums.size()-1 left=nums.size(),right=nums.size()1,而要返回的位置为最后一个元素后面,即索引位置为 n u m s . s i z e ( ) nums.size() nums.size(),因此返回 r i g h t + 1 right + 1 right+1
  • 插入到中间元素的左边:即 t a r g e t target target比当前 m i d d l e middle middle对应的元素小,此时已经是 l e f t = = r i g h t left == right left==right,因此更新为 r i g h t = m i d d l e − 1 right=middle-1 right=middle1,而正确的插入位置就是 m i d d l e middle middle,因此返回 r i g h t + 1 right+1 right+1
  • 插入到中间元素的右边:即 t a r g e t target target比当前 m i d d l e middle middle对应的元素大,此时已经是 l e f t = = r i g h t = = m i d d l e left == right ==middle left==right==middle,因此更新为 l e f t = m i d d l e + 1 left=middle+1 left=middle+1,此时位置为 r i g h t = m i d d l e right=middle right=middle,而正确的插入位置就是 m i d d l e + 1 middle+1 middle+1,因此回 r i g h t + 1 right+1 right+1

34、在排序数据中查找元素的第一个和最后一个位置

该题目一开始的思路是想着初始化数组然后左右边界同时判断,但觉得能力有限逻辑不清楚,因此看了代码随想录的提醒,即左右边界分开求解便思路清晰,具体代码如下:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int rightBorder = getRightBorder(nums,target);
        int leftBorder = getRLeftBorder(nums,target);
        if (rightBorder == -1 || leftBorder == -1){
            return {-1,-1};
        }else{
            return {leftBorder,rightBorder};
        }

    }
    int getRightBorder(vector<int>& nums,int target){
        int rightBorder = -1;
        int left = 0;
        int right = nums.size()-1;
        while(left <= right){
            int middle = (left + right) / 2;
            if( nums[middle] < target ){
                left = middle+1;
            }else if( nums[middle] > target ){
                right = middle-1;
            }else{
                rightBorder = middle;
                left = middle+1;
            }
        }
        return rightBorder;
    }
    int getRLeftBorder(vector<int>& nums,int target){
        int leftBorder = nums.size();
        int left = 0;
        int right = nums.size()-1;
        while(left <= right){
            int middle = (left + right) / 2;
            if( nums[middle] < target ){
                left = middle+1;
            }else if( nums[middle] > target ){
                right = middle-1;
            }else{
                leftBorder = middle;
                right = middle-1;
            }
        }
        return leftBorder;
    }
};

主要思路是初始化边界都为 − 1 -1 1,只要其中存在与 t a r g e t target target相同的元素那么就一定能够通过二分法查找到,就一定会修改边界,因此只要返回的两个边界不为 − 1 -1 1就直接输出两个边界即可。返回的若其中一个是 − 1 -1 1那么就要输出都是 − 1 -1 1

其次就是判断边界中我的边界更新条件与代码随想录中的不同,但都是可以通过的。

69、x的平方根

同样是用二分查找,主要是longlong数据类型的使用

class Solution {
public:
    int mySqrt(int x) {
        int left = 0;
        int right = x;
        int ans = -1;
        while(left <= right){
            int middle = (left + right) / 2;
            if ((long long) middle * middle <= x){
                ans = middle;
                left = middle + 1;
            }else{
                right = middle - 1;
            }
        }
        return ans;
    }
};

以下代码是不行的:

int middle = (left + right) / 2;
long long middlePow = middle * middle;

这样会显示溢出,在 m i d d l e ∗ m i d d l e middle*middle middlemiddle的时候,因为是 i n t int int类型,在相乘之后也想要返回一个 i n t int int类型,但是太大的数字在这里就发生了溢出,因此就算是一个 l o n g l o n g long \quad long longlong类型来接收也不行。可以这样做:

int middle = (left + right) / 2;
long long middlePow = (long long)middle * middle;

在返回之前就用一个 ( l o n g l o n g ) (long \quad long) (longlong)来将返回值的类型修改,防止溢出。

367、有效的完全平方数

这部分耶可以用二分查找完成,具体逻辑只是在找到的时候返回值不同而已,找到我们立刻返回一个 t r u e true true,如果正常退出循环即说明没有找到,因此返回 f a l s e false false

class Solution {
public:
    bool isPerfectSquare(int num) {
        int left = 0;
        int right = num;
        
        while(left <= right){
            int middle = (left + right) / 2;
            long long middlePow = (long long)middle * middle;
            if(middlePow < num){
                left = middle + 1;
            }else if(middlePow > num){
                right = middle - 1;
            }else{
                return true;
            }
        }
        return false;
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值