旅行者手札2:二分查找套用模板即可解决

先说点题外话:
不知不过在公司干了半年,转正之后连拿了两个S评级,这半年工作熟悉业务,顺便还因为机缘巧合从三月开始基本掌握了React+Flask+数据库的开发。所以很久没更新了,现在工作基本稳定下来,准备继续把408复习复习,重新夯实基础。

进入正题,咱们跟着LEETCODE重新从算法基础看起。

https://leetcode-cn.com/study-plan/algorithms/

首先是算法入门—二分查找

二分查找的原理比较简单,就是每次折半,直到定位到我们要的答案。因为它如此简单,所以时间复杂度是O(logn)。
但就像《一拳超人》,埼玉老师强大的代价是秃头,二分查找强大的代价就是让它在实际工作中几乎用不上的限制条件:目标必须是有序的。
因此二分查找的规律也是非常的明显

在这里插入图片描述
先不放问题,来看看我这几题的解答(C++直球式写法):

//第一题
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int start = 0, end = nums.size() - 1;
        int mid = end / 2;
        if(nums[mid] == target)
            return mid;
        while(start != end)
        {
            if(target > nums[mid])
                start = mid + 1;
            else if(target < nums[mid])
                end = mid; 
            mid = (end - start) / 2 + start;
            if(nums[mid] == target)
                return mid;       
        }
        return -1;
    }
};

//第二题
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        if(n == 1)
            return 1;
        int start = 0, end = n-1;
        int mid = end / 2;
        while(start != end)
        {
            if(isBadVersion(mid + 1) == true)
                end = mid;
            else if(isBadVersion(mid + 1) == false)
                start = mid + 1;
            mid = (end - start)/2 + start;
        }
        return start + 1; 
   
    }
};

//第三题
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int start = 0, end = nums.size() - 1;
        int mid = end / 2;
        while(start != end)
        {
            if(target > nums[mid])
                start = mid + 1;
            else if(target < nums[mid])
                end = mid;
            mid = (end - start) /2 + start;
            if(nums[mid] == target)
                return mid;
        }
        if(target > nums[start])
            return start + 1;
        else 
            return start;
    }
};

可以注意到,二分查找问题的思路有一个固定的代码模板:

//此处为伪代码,假设这是在找一个有序数组array中的数target(即数组中的数必定包含我们要的数)
int start = 0, end = n-1; //n是要读取的有序数组的长度
int mid = end - start / 2; //注意c++ integer向下取整
while(start != end)
{
	if(array[mid] > target)
		end = mid;//如果数组中值大于target,则说明目标在mid前面,折半操作通过end移动到mid
	else if (array[mid] < target)
		start = mid + 1;//如果数组中值小于target,则说明目标在mid后面,折半操作通过start移动到mid+1
	mid = (end - start) / 2 + start;//折半后不要忘记更新中点坐标
}
return start; //当起点终点在循环中重合,说明最后剩下的数字就是我们的答案。

一切的二分法都可以从上面的基础代码修改完成,首先说一下大家很容易迷糊(包括我)的index,即数组下标。
首先这个代码比较迷糊的地方是:为什么end = mid而start = mid + 1。
首先我们要明白,把算法转换为实际是需要一个起点的,而这里的起点就是
start = 0, end = n - 1, mid = (end - start) / 2 + start
在这个基础上,我们只需要考虑一种折半的情况是否能走通while(start != end),而不会导致无限循环即可:

当array = [0, 1]的时候,因为理想中所有二分法的最后一步都是这个(不考虑中途找到结果)

以这个数组为例,array = [0, 1]; start = 0; end = 1; mid = 0
因此array[mid] = array[0]。

如果target < array[mid]。

根据上面的基础代码,我需要把end往前折半,即end = mid = 0 = start,这个时候while(start != end)就能走出循环,那么我们上面的基础代码
if(array[mid] > target) end = mid;
这个判断在这个是没有问题的

如果target > array[mid]。

根据上面的基础代码,我需要把start往后折半,即start = mid + 1 = 1 = end, 这个时候这个时候while(start != end)就能走出
循环,那么我们上面的基础代码
else if (array[mid] < target) start = mid + 1;
这个判断在这个是没有问题的

因此为什么循环中end = mid而start = mid + 1,这是为了锚定唯一值,脱出二分循环。


接下来我们回顾一下LEETCODE的问题

LEETCODE中的算法题算是基础算法的思路扩张:

1.如果数组不一定包含我们要的值怎么办:

在这里插入图片描述

//第一题
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int start = 0, end = nums.size() - 1;
        int mid = end / 2;
        if(nums[mid] == target)
            return mid;//以防数组长度为1但是唯一的值又刚好等于结果走下面的循环直接跳出输出-1
        while(start != end)
        {
            if(target > nums[mid])
                start = mid + 1;
            else if(target < nums[mid])
                end = mid; 
            mid = (end - start) / 2 + start;
            if(nums[mid] == target)
                return mid;//这一步包含了最后的start=end=mid时候的判断,如果定位到数组只剩一个值,但是nums[mid]依旧和target不相等,那就说明数组没有target。       
        }
        return -1;
    }
};
2.我们如何去抽象概括出数组来使用二分法

在这里插入图片描述

//第二题 抽象有难度,但是几乎是直接套用
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        if(n == 1)
            return 1;
        int start = 0, end = n-1;//将n看作0~n-1的数组套用公式即可
        int mid = end / 2;
        while(start != end)
        {
            if(isBadVersion(mid + 1) == true)
                end = mid;
            else if(isBadVersion(mid + 1) == false)
                start = mid + 1;
            mid = (end - start)/2 + start;
        }
        return start + 1; 
   
    }
};
3.当返回值变化时的二分查找

在这里插入图片描述

//第三题
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int start = 0, end = nums.size() - 1;
        int mid = end / 2;
        while(start != end)
        {
            if(target > nums[mid])
                start = mid + 1;
            else if(target < nums[mid])
                end = mid;
            mid = (end - start) /2 + start;
            if(nums[mid] == target)
                return mid;
        }
        if(target > nums[start])//有两种输出怎么办?写个if----else即可啦
            return start + 1;
        else 
            return start;
    }
};

可以看到,所有题目基本都是套用公式,以后看到二分查找不用怕,记住基础公式即可啦~:

//此处为伪代码,假设这是在找一个有序数组array中的数target(即数组中的数必定包含我们要的数)
int start = 0, end = n-1; //n是要读取的有序数组的长度
int mid = end - start / 2; //注意c++ integer向下取整
while(start != end)
{
	if(array[mid] > target)
		end = mid;//如果数组中值大于target,则说明目标在mid前面,折半操作通过end移动到mid
	else if (array[mid] < target)
		start = mid + 1;//如果数组中值小于target,则说明目标在mid后面,折半操作通过start移动到mid+1
	mid = (end - start) / 2 + start;//折半后不要忘记更新中点坐标
}
return start; //当起点终点在循环中重合,说明最后剩下的数字就是我们的答案。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值