【旋转数组】二分法专题

讲解

左闭右闭:
left = 0, right = nums.size() - 1
找target
进入while循环,while(),闭区间[1, 1]合法吗?合法,那么
while(left <= right)
mid = (left + right) / 2
if(mid > target) right = mid - 1;//不用=,因为mid一定不是
else if(mid < target) left = mid + 1;
else return mid
跳出while循环之后,return -1

左闭右开:
left = 0,right = nums.size()
[1,1)合法吗?不合法!所以while(left < right),不可以等于
mid = (left + right) / 2
if(mid > target) right = mid
else if(mid < target) left = mid + 1;
else return mid
跳出while,return -1

左开右开:
left = 0, right = nums.size()
while(left + 1 != right)
mid = (left + right) / 2;
if(mid > target) right = mid
else left = mid;
跳出while,return right

()是最容易的写法

寻找旋转排序数组中的最小值

无重复元素,所有元素都不一样

在这里插入图片描述
旋转数组这一系列题有一点非常怪,tmd,那就是12345不可能旋转成54321,所以开区间那边都是nums.size() - 1

二分法需要保证的不是连续性,而是二段性质,简单点来说,就是这序列的数组有没有一个明显的界限分成两部分,这个题显然有,那么直接套模板是可以的,只是在修改left和right的时候需要注意

咱要找的是最小值,最小值在哪一段?右边那一段,所以我们选择nums.back()作为比较点,接下来,以开区间作为咱的写法,left = -1, right = nums.size() - 1,这里right为什么这么写上面已经提过了

if(nums[mid] < nums.back()),说明咱们要找的最小值的点还在更左边,那么right = mid
else left = mid;

最后仍然return nums[right]

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

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

有重复元素~!

普通的二分法也只是要求数组有序而没有说必须无重复,但是在旋转数组中,咱们如果有重复数组的话会麻烦很多,主要体现在二段性的缺失。
举一个缺失二段线的例子:
在这里插入图片描述
由于在旋转点有元素重复,所以左边大于等于2,邮编小于等于2,都有等于2的元素,导致没办法分成特点鲜明的两端,那么怎么做呢?把二段性补回来即可

怎么补?也不简单,首先这个题很变态,12345是有效用例,54321就是无效的
所以假设哈,假设

开头和结尾出现了重复数,比如[5,6,7,1,2,3,4,5,5],第一段大于等于5,第二段小于等于5,这种情况right–和left++,将5去除,尚能解决,由于我说过了12345在这里是有效用例,54321就不是,所以我们选择right–

class Solution {
public:
    int findMin(vector<int>& nums) {
        // 恢复二段性
        int n = nums.size();
        int left = -1, right = n - 1;
        while(left < right && nums[right] == nums[0]){
            right--;
        }
        int flag = right;
        // 找到旋转点
        while (left + 1 < right){
            int mid = (left + right) / 2;
            if(nums[mid] < nums[flag]){
                right = mid;
            } 
            else{
                left = mid;
            }
        }
        return right == -1 ? nums[0] : nums[right];
    }
};

在这里插入图片描述
以上right–可以解决头尾重复,但无法解决12334这种情况
为什么呢?
首先这种情况不在头尾重复缺失二段性的例子里面,当与back比较的时候,如果相等,按照上面的代码我们会使left = mid,但是最小值显然在左边,我们要改的是right才对

class Solution {
public:
    int findMin(vector<int>& nums) {
        // 恢复二段性
        int n = nums.size();
        int left = -1, right = n - 1;
        while(left < right && nums[right] == nums[0]){
            right--;
        }
        int flag = right;
        // 找到旋转点
        while (left + 1 < right){
            int mid = (left + right) / 2;
            if(nums[mid] <= nums[flag]){
                right = mid;
            } 
            else{
                left = mid;
            }
        }
        return right == -1 ? nums[0] : nums[right];
    }
};

小总结

以上两道题我小总结一下,有无重复元素影响的是=加不加,其实第一题也可以加,在无重复元素的时候,开区间加不加其实没所谓,但是当有重复元素的时候,考虑这个很重要

我在刚开始学习二分法的时候,背诵的模板的不加等号的if,改的是left,其实我如果照着这个模板写这两道题,就不会错,但是我鬼使神差把不加等号那边改right了,导致一直不过

那么可以总结一个有无重复元素通用的模板

针对第一题,第二题是一样的:

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

搜索选择排序数组

无重复元素,找target下标

先找最小值,然后和最小值比较,往哪边找
开区间改left是好习惯!

class Solution {
public:
    int findMin(vector<int>& nums){
        int left = -1, right = nums.size() - 1;
        while(left + 1 != right){
            int mid = (right + left) / 2;
            if(nums[mid] > nums.back()) left = mid;
            else right = mid;
        }
        return right;
    }
    int lower_bound(vector<int>& nums, int left, int right, int target){
        while(left + 1 != right){
            int mid = (left + right) / 2;
            if(nums[mid] < target) left = mid;
            else right = mid;
        }
        if(right == nums.size()) return -1;
        return nums[right] == target ? right : -1;
    }
    int search(vector<int>& nums, int target) {
        int index = findMin(nums);
        if(target > nums.back()) return lower_bound(nums, -1, index, target);
        else return lower_bound(nums, index - 1, nums.size(), target);

    }
};

搜索旋转排序数组II

有重复元素,找target下标

哎呀,这题就好难了

如果按照之前的做法,会败在用例[2,2,2,3,2,2,2]
为什么呢?

因为后面的2在一开始就被right–掉了,剩下前面一节2和3,这时候返回的最小值一定是第一个2,即index = 0,要找的是3,-1和index之间哪里有3呢?

理想情况,我们要找的最小值应该是[2,2,2,3,2,2,2]中间标黄的2

md这就是这一系列题很恶心的地方,写的烦死了

之后我想到一个完美的解决方案过这个用例,那就是,两边都return一下,只要有true就是true,都是false才是false,完美!
更改的关键代码:

if(!lower_bound(nums, -1, index, target)){
  return lower_bound(nums, index - 1, flag, target);
}
else return true;  

新的问题来了,right–后相当于后面一节相似的不要了,假如数组是22222的话,right会一直减,直到为-1,那么flag也会是-1,之后right可以在return部分纠正为0,但flag还会是-1,所以这里的flag需要纠正:

flag = right;
if(right == -1) flag = 0;

总代码:

class Solution {
public:
    int flag;
    int findMin(vector<int>& nums){
        int left = -1, right = nums.size() - 1;
        while(left < right && nums[right] == nums[0]) right--;
        flag = right;
        if(right == -1) flag = 0;
        while(left + 1 < right){
            int mid = (right + left) / 2;
            if(nums[mid] > nums[right]) left = mid;
            else right = mid;
        }
        return right == -1 ? 0 : right;
    }
    bool lower_bound(vector<int>& nums, int left, int right, int target){
        while(left + 1 < right){
            int mid = (left + right) / 2;
            if(nums[mid] < target) left = mid;
            else right = mid;
        }
        if(right == nums.size()) return false;
        return nums[right] == target ? true : false;
    }
    bool search(vector<int>& nums, int target) {
        int index = findMin(nums);
        if(!lower_bound(nums, -1, index, target)){
            return lower_bound(nums, index - 1, flag, target);
        }else{
            return true;
        }
    }
};

小总结

说实话,我感觉自己在面向用例编程,最后的代码并不优美,但也是我自己一点点想出来的,也更加熟悉了二分法,每道题都比前面的题目多一点点特殊情况,很头疼,我在想,这种题目考我的话,思考还不如背诵,思考我还需要用例,但是换个环境哪有用例?找别人优美的代码学一学,背一背,可能比自己孤军奋战折腾出来更好,最后附上自己的提交记录,因为真的不容易。。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值