学习目标:
每天睡前是否感到浑浑噩噩,一天又在不知不觉中过去,回想我今天都干了什么呢?
啊~我这一天又什么也没干,好有罪恶感啊,不行,我明天一定要好好学算法(手动狗头)。
明日复明日,明日何其多?不要等明天啦,和小编一起,每天睡前一道算法题,不仅解决你一天的空虚,更能助你安心入眠,远离熬夜。还能学到一点算法知识。不要小看这些知识哦,不积跬步无以至千里,不积小流无以成江海。每位大佬都不是一夜成名,都是从小白做起,日积月累,终成大佬,和小编一起,每日一题,走向大佬之路吧!
学习内容:
我们的题基本都是一些较为基础,适合日(睡)常(前)看的题,不会让你一眼就看出答案,是稍稍一点脚就能摸到头绪,但是在想的过程中又一时不知如何实现,再一想,又柳暗花明,最终跟着小编的思路一起解决问题,有不同思路可以发在评论区,一起讨论。“你若在,我必回”。
我们今天的题目是一道二分查找的问题,在算法优化中,我们常用的一种思想就是二分思想,我们今天不仅是讲解二分查找的题目,还有对于二分思想的理解。我们从今天的题目入手,以讲题目为例讲解二分的思想。看下题目:
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 1:输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:输入:nums = [], target = 0
输出:[-1,-1]提示:
0 <= nums.length <= 10^5
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109
看完题目后,我们知道是让我们找到target在数组中的起始坐标和终点坐标。如果用正常思想很容易就是一个for循环就可以解决的问题,但是我们算法的目的就是不断优化,刚才简单的方法时间复杂度是O(n),我们能否找到一个时间复杂的更优的方法呢?
答案是肯定的。我们看下题目还有那些信息没用上?是的,数组是非递减的,也就是说他是有序的,而我们的二分也正是基于这一点,二分的思想主要体现在对有序数组的不断缩小范围,当第i个数大于target,那么我们要找到数就一定在第i个以前,从而将搜索范围缩小到0~i,而二分也就是不断将数据折半,从而快速减少数据量。
回到我们的题目,因为数组有序,我们要找的就是target第一次出现的坐标和最后一次出现的坐标,那么我们是不是可以分别去求,用一个函数去找头,另一个去找尾。而且方法都是相同的,只是判断条件有所不同。以找头为例:
int findl(vector<int>& nums, int target,int l,int r){
if(l>=r && nums[l]!=target) return -1;
int mid=(l+r)/2;
if(nums[mid]==target && (mid==0 || nums[mid-1]!=target)) return mid;
else if(nums[mid]>=target) r=mid-1;
else if(nums[mid]<target) l=mid+1;
return findl(nums,target,l,r);
}
nums是数组,target是目标值,l,r是搜索区间。
当我们找到了mid,使nums[mid]=target,什么时候它使开始的坐标,那就是前一个数!=target或者坐标为0,那么此时这个mid就是我们要找的那个值;如果nums[mid]>target,也就说明我们要找的数在mid以前,我们就可以缩小区间,令r=mid-1;如果nums[mid]<target,也就说明我们要找的数在mid以后,我们就可以缩小区间,令l=mid+1;还有一种情况就是nums[mid]=target但不满足前一个数!=target或者坐标为0的条件,那这个应该怎么缩小区间呢?这就要看我们的目的是什么了?因为是要找到头,所以我们要将搜索区间向前移动,令r=mid-1。然后进行递归寻找。
然后找尾的方法和找头近乎相同,就是判断条件改一下,这就交给你来完成啦!自己手动一边比看的效果要强很多的。点击下方链接可以自己做一遍这道题,会有更多收获的!
例题https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/最后就是我们函数的整体:
class Solution {
public:
int findl(vector<int>& nums, int target,int l,int r){
if(l>=r && nums[l]!=target) return -1;
int mid=(l+r)/2;
if(nums[mid]==target && (mid==0 || nums[mid-1]!=target)) return mid;
else if(nums[mid]>=target) r=mid-1;
else if(nums[mid]<target) l=mid+1;
return findl(nums,target,l,r);
}
int findr(vector<int>& nums, int target,int l,int r){
if(l>=r && nums[l]!=target) return -1;
int mid=(l+r)/2;
if(nums[mid]==target && (mid==nums.size()-1 || nums[mid+1]!=target)) return mid;
else if(nums[mid]>target) r=mid-1;
else if(nums[mid]<=target) l=mid+1;
return findr(nums,target,l,r);
}
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> res(2,-1);
if(nums.size()==0) return res;
int r=nums.size()-1;
res[0]=findl(nums,target,0,r);
res[1]=findr(nums,target,0,r);
return res;
}
};
注意搜索不到的判断条件和返回值。
每天坚持是一件很累的事,即使很慢,但也不要停止。
大家记得点赞收藏,防止找不到哦。
欢迎大家订阅小编的每日一题专栏,会每天更新,都是用心准备的哦!
若有不同思路,欢迎评论区留言,看到必回,Goodnight!