算法大陆基础算法篇------小锤穿越学二分

第一章

   马三是华夏某一所不知名大学的计算机系的学生,没有什么爱好,唯独爱看小说,而他最近迷上了一本很火的小说叫做 算法大陆,小说生动的剧情令他欲罢不能,他已经连续3天没有睡觉了,就在这天凌晨,电闪雷鸣,天上下着暴雨,马三终于看完了这本小说,长叹一口气,闭上了双眼,就这样睡了过去......

           算法大陆,基础算法帝国,二分省,菜鸟村。从马三昏睡过后,来到这个世界已经五年多了,没错他穿越了,他有个新的名字就叫小锤,因为他的父亲是个铁匠,是附近有名的锤王,这天锤王把马三叫到身边语重心长的说:你未来想做什么,马三想了想道:我要做算法师,在马三说出这句话时,锤王双眼一亮,算法师,是算法大陆最崇高的职业,锤王眼神逐渐迷离,仿佛陷入了回忆,咳咳,你决定好了嘛,学算法这条路非常艰辛,你一旦选择了,便不能回头了,那好,从今天开始,我来教你算法,但这件事,你不许对任何人说。马三内心笑道:果然有金手指,就在他幻想的时候,锤王说道:择日不如撞日,开始今天的训练吧------二分

马三二分是什么?

锤王 :二分查找又称折半查找,是一种效率较高的查找方法,我们通过一个题目来了解一下

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

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
复制代码

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
复制代码

锤王这道题你知道怎么做吗?

马三 :我在for循环中遍历数组nums,取出每一个与target比较是否相同

class Solution { 
public int search(int[] nums, int target) { 
for(int i = 0; i < nums.length; i ++ ) 
if(nums[i] == target)
return i;
return -1; 
     }
 }
复制代码

锤王:没错,这样当然是可以的,如果把每个数都进行比较,这样的时间复杂度是O(n),还有时间复杂度更低的方法吗?

马三 :哈哈,我想不到了。

锤王:在一个范围内,查找一个数字,要求找到这个元素的开始位置和结束位置,这个范围内的数字都是单调递增的,即具有单调性质,因此可以使用二分来做。它的时间复杂度是log(n),接下来我来给你俩个模板,以后你碰见所有的二分都可以靠这俩个模板解决。

二分模板:

模板1 当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1,计算mid时不需要加1,即mid = (l + r)/2, 这段代码求出的是满足性质的左边界

int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = (l + r)/2;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}
复制代码

模板2

当我们将区间[l, r]划分成[l, mid - 1]]和[mid, r]时,其更新操作是r = mid - 1或者l = mid,此时为了防止死循环,计算mid时需要加1,即mid = ( l + r + 1 ) /2,这段代码求出的是满足性质的右边界。

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = ( l + r + 1 ) /2;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}
复制代码

马三 :为什么两个二分模板mid取值不同?

锤王:对于第二个模板,当我们更新区间时,如果左边界ll更新为l = mid,此时mid的取值就应为mid = (l + r + 1)/ 2。因为当右边界r = l + 1时,此时mid = (l + l + 1)/2,下取整,mid仍为l,左边界再次更新为l = mid = l,相当于没有变化,while循环就会陷入死循环。因此,我们总结出来一个小技巧,当左边界要更新为l = mid时,我们就令 mid =(l + r + 1)/2,上取整,此时就不会因为r取特殊值r = l + 1而陷入死循环了。

而对于第一个模板,如果左边界l更新为l = mid + 1,是不会出现这样的困扰的。因此,大家可以熟记这两个二分模板,基本上可以解决99%以上的二分问题,再也不会被二分的边界取值所困扰了。

马三 :为什么模板要取while( l < r),而不是while( l <= r)?

锤王:本质上取l < r 和 l <= r是没有任何区别的,只是习惯问题,如果取l <= r,只需要修改对应的更新区间即可。

马三 :那我们这题应该用哪个模板呢?

锤王:假设初始时我们的二分区间为[l,r],每次二分缩小区间时,如果左边界l要更新为 l = mid,此时我们就要使用模板2,让 mid = (l + r + 1)/ 2,否则while会陷入死循环。如果左边界l更新为l = mid + 1,此时我们就使用模板1,让mid = (l + r)/2。因此,模板1和模板2本质上是根据代码来区分的,而不是应用场景。如果写完之后发现是l = mid,那么在计算mid时需要加上1,否则如果写完之后发现是l = mid + 1,那么在计算mid时不能加1。

锤王:让我们分别用俩个代码实践一下

模板一

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l=0,r=nums.size()-1;
        while(l<r){
            int mid = l+r>>1;
            if(nums[mid]>=target) r=mid;
            else l=mid+1;

        }
        if(nums[l]==target) return r;
        else return -1;

    }
};

复制代码

模板二

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l=0,r=nums.size()-1;
        while(l<r){
            int mid = l+r +1>>1;
            if(nums[mid]<=target) l=mid;
            else r=mid - 1;

        }
        if(nums[l]==target) return r;
        else return -1;

    }
};
复制代码

马三 : while循环结束条件是l >= r,但为什么二分结束时我们优先取r而不是l?

锤王 :二分的while循环的结束条件是l >= r,所以在循环结束时l有可能会大于r,此时就可能导致越界,因此,基本上二分问题优先取r都不会翻车。

锤王:这次先到这里,要是还不会的话,


 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值