二分法查找是基于有序_专题系列——二分查找

ef9526fe51c2a0ba02031159d12842c0.png

二分查找的边界条件找的我怀疑人生 嘤嘤嘤

一、LeetCode69 求开方

这个题比较简单,二分查找的问题,一开始出错了,进了循环出不来,是因为当mid * mid大于x的时候,赋值right应该是mid- 1,而不是mid,因为它是向下取整的,所以是mid-1一定是小于等于开方数字的,其次就是二分的时候向右取一位,再就是要用longlong类型,否则会溢出。

class Solution {
public:
    int mySqrt(int x) {
        // 用二分查找的方法,设置left和right,如果mid的平方大于x,那就更新right,如果mid平方小于x,那就更新left,直到left = right,就返回即可
        int left = 0;
        int right = x / 2 + 1;
        while(left < right){
            long long mid = 1 + left + (right - left) / 2;
            long long sq = mid * mid;
            if(sq > x) right = mid - 1;
            else left = mid;
        }
        return left;
    }
};

二、LeetCode744 寻找比目标字母大的最小字母

这个题,晕晕的,到底left和right和mid到底应该如何赋值呢,right肯定是size - 1,mid啥时候是向右啥时候向左呢,上一个题是向右取得,这个题就是向左取得,这个题是left 是mid + 1,上一个题是right 赋值为mid - 1,

所以目前来看,应该是mid向右取,那就right是mid - 1, 如果mid向左取,那就是left是mid + 1

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

三、LeetCode540. 有序数组中的单一元素

这个题也是墨迹了好久啊,都怪自己没想清楚,反正就是二分的条件一般都是left<right的,然后这个题一开始没有考虑到二分以后两边的数字到底是偶数还是奇数的情况,只考虑了两边是偶数的情况。

然后觉得如果中间的数和左边相等,那单独的数字就在左边,如果中间的数和右边相等(left = mid + 2),那单独的数字就在右边.

但其实这是mid是奇数个时候,如果mid是偶数个时候,也就是两边是奇数的时候,那mid和左边相等,单独的数就在右边(left = mid + 1),mid和右边相等,那单独的数就在左边。

而且对left和right重新赋值的时候也要想清楚,这里主要是考虑只要把对应的已经确定不是单独数字的那个给跳过去就好了,所以这里有一个加2和减2的过程。

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        // 如果全部出现2次,肯定是一个偶数长度的nums,这样先二分求mid,mid和左边相等,说明这个数字在左边,如果和右边相等,说明这个数字在右边,都不相等就说明找到了这个数字
        int left = 0, right = nums.size() - 1;
        while(left < right){
            int mid = left + (right - left) / 2;
            if(mid % 2 == 0 && nums[mid] == nums[mid - 1]) right = mid - 2;
            else if(mid % 2 == 0 && nums[mid] == nums[mid + 1]) left = mid + 2;
            else if(mid % 2 != 0 && nums[mid] == nums[mid - 1]) left = mid + 1;
            else if(mid % 2 != 0 && nums[mid] == nums[mid + 1]) right = mid - 1; 
            else return nums[mid];
        }
        return nums[left];      
    }
};

四、LeetCode278 第一个错误的版本

这是一个单纯的二分查找的题目,所以应该就是mid如果是往左的,那就是左边是mid + 1,如果是往右的,那右边就是mid - 1,并且这个更新左边还是更新右边有1,要看具体情况,比如这个题,mid如果是错误的,那这个mid有可能是第一个错误版本,那就需要在下一次循环中考虑上它,但是如果mid不是错误的,那它一定不是第一个错误版本,那就不需要再下一次循环中考虑上它。

// Forward declaration of isBadVersion API.
bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        int left = 1;
        int right = n;
        while(left < right){
            int mid = left + (right - left) / 2;
            if(isBadVersion(mid)) right = mid;
            else left = mid + 1;
        }
        return left;
    }
};

五、LeetCode 153 寻找旋转排序数组中的最小值

这个题其实和之前做的33题 搜索旋转排序数组 很像,所以一定是可以用二分来查找的。

方法一:遍历数组,时间复杂度是O(n)

class Solution {
public:
    int findMin(vector<int>& nums) {
        int minVal = nums[0];
        for(int i = 1; i < nums.size(); i++){
            minVal = minVal <= nums[i] ? minVal : nums[i];
        }
        return minVal;
    }
};

方法二:类二分法查找

找到中间位置,如果中间位置小于第一个数字,那就说明前半部分包含旋转部分,那最小值一定在前半部分,也就是更新right的值。

如果中间位置数字大于第一个数,这说明前半部分有序,后半部分的情况是不确定的,如果后半部分也是有序的,那最小值就是第一个left位置的值,否则的话最小值应该是出现在后半部分无序的部分中。

因此,这时候要比较num[right]和mid的值大小,如果nums[right] > nums[mid],说明后半部分有序,这时候nums[left]就是最小值。否则的话,应该去后半部分寻找最小值,也就是更新left的值。

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

六、LeetCode34 在排序数组中查找元素的第一个和最后一个位置

这个题的思路不难,但是我二分的边界条件老找不准,看来如果left和right同时对1操作,那循环条件就变成了有可能相等了。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        // logn复杂度肯定是二分查找了。
        vector<int> res(2, -1);
        int left = 0, right = nums.size() - 1;
        while(left <= right){
            int mid = left + (right - left) / 2;
            if(nums[mid] > target) right = mid - 1;
            else if(nums[mid] < target) left = mid + 1;
            else{
                // nums[mid] == target的情况,就找到首尾位置
                int temp = mid;
                while(temp >= left && nums[temp] == target) temp--;
                while(mid <= right && nums[mid] == target) mid++;
                res[0] = temp + 1;
                res[1] = mid - 1;
                break;
            }
        }
        return res; 
    }
};

七、LeetCode154 寻找旋转排序数组中的最小值 II

这个题,,,我虽然ac了,但我也不知道自己在写啥东西了,,就是根据测试样例,才能够想到那些边界情况,要是直接白板写感觉根本搞不定

class Solution {
public:
    int findMin(vector<int>& nums) {
        // 虽然存在重复的元素,但本质还是二分查找。还是分情况讨论,如何更新left和right的情况
        int left = 0;
        int right = nums.size() - 1;
        while(left < right){
            int mid = left + (right - left) / 2;
            if(nums[left] <= nums[mid] && nums[left] < nums[right]) return nums[left];
            else if(nums[left] <= nums[mid] && nums[mid] > nums[right]) left = mid + 1;
            else if(nums[left] > nums[mid]) right = mid;
            else{
                // 这里对应的是nums[left] <= nums[mid] && nums[left] == nums[right]
                int temp = mid;
                while(temp <= right && nums[temp] == nums[mid]) temp++;
                if(temp - 1 == right){
                    // 说明右边是有序的
                    temp = mid;
                    while(temp >= left && nums[temp] == nums[mid]) temp--;
                    if(temp + 1 == 0) return nums[left];
                    else right = temp + 1;
                }
                else left = temp;
            }
        }
        return nums[left];
    }
};

看了一个大神的思路,感觉太腻害了,,它这个就是严格的二分,而我原来那些其实都不是严格的二分,我是在找递增的区间,然后直接返回最左边的值。

力扣​leetcode-cn.com

要找的是第二个数组的首个元素,所以nums[mid]就和num[right]进行比较,

nums[mid] > num[right] ,说明旋转点在右边,更新left = mid + 1

nums[mid] < num[right],说明要么旋转点在左边,要么旋转点就是mid,所以更新right = mid

当二者相等的时候,就更新right = right - 1

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;
        while(left < right){
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[right]) left = mid + 1;
            else if(nums[mid] < nums[right]) right = mid;
            else right = right - 1; 
        }
        return nums[left];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值