明摆着挺经典的二分题。
用我以前做过的题,能直接想出这样思路:二分找到小于目标的最大值,再找到大于目标的最小值。然后由此得到题目要求。思路很简单,细节是魔鬼。
麻溜的写出了这样的代码
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int len = nums.size();
int l=0, r=len-1;
while(l<=r){
int mid = l+(r-l)/2;
if(nums[mid] >= target)
r = mid - 1;
else l = mid + 1;
}
int ansmin = r;
l = 0, r = len-1;
while(l<=r){
int mid = l+(r-l)/2;
if(nums[mid] <= target) l = mid + 1;
else r = mid - 1;
}
int a, b;
int ansmax = l;
if(ansmin == -1){
if(ansmax == len){
if(len>0) a=0, b=len-1;
else a=-1, b=-1;
}
else {
if(ansmax == 0) a=-1, b=-1;
else a=0, b=ansmax-1;
}
}
else{
if(ansmax == len){
if(ansmin == len-1) a=-1, b=-1;
else a=ansmin+1, b=len-1;
}
else{
if(ansmax-ansmin == 1) a=-1, b=-1;
else a=ansmin+1, b=ansmax-1;
}
}
vector<int> v;
v.push_back(a), v.push_back(b);
return v;
}
};
通过样例发现了写小bug,改完之后一发入魂,很舒服。
但是其实这样在写的时候一点都不舒服。要把每一种情况都列出来,采用三层if的结构才能写清楚。因为找到第一个小于目标的最大值后,还要确保到底存不存在目标值,总之很繁琐,不能算错,但很繁琐。
我写完也感觉很繁琐,于是看了看评论,才发现可以用二分控制while()里面的逻辑表达式,以此来找到目标的左边界和有边界。
以前只会写while(l<=r)这样的形式,并且保证这种情况可以写清楚下面的各种细节,经过不断的归纳总结思考,可以做到。但是却并没有去归纳总结关于while(l<r)这样的形式。本来认为这两种能实现的功能一毛一样,只需处理细节就行,其实并不是。
比如这题,可以直接通过while(l<r)的形式找到符合目标的左边界,而while(l<=r)却并不行,,,,
原因:while(l<=r)这种形式,下面不能出现r=mid,不然就会陷入无限循环的情况,仔细列举几种情况很好想出来。而不能用r=mid就不能把目标值保留下来。也就无法找到符合目标的左边界。
于是有了下面的代码:顺便用了刚学到的一些c++知识,舒服。
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> v(2, -1);
if(nums.empty()) return v;
int len = nums.size();
int l = 0, r = len-1;
while(l < r){
int mid = (l+r)/2;
if(nums[mid] >= target) r = mid;//注意这里
else l = mid+1;//注意这里
}
if(nums[l] != target) return v;
v[0] = l;
r = len;
while(l < r){
int mid = (l+r) / 2;
if(nums[mid]<=target) l = mid+1;
else r = mid;
}
v[1] = l-1;
return v;
}
};
真·短小精悍
总算感觉自己真正完整的会用了二分了。
总结一下关于二分算法
二分主要考察人的,我觉得不是这个算法的思想(这个算法的思想真的挺简单的)而是细节,关于while里面的逻辑运算的细节,关于中间值和目标比较的逻辑运算的细节以及条件成功之后l和r的走势的细节。
弄懂每个细节到底为什么,那二分自此以后再也没有任何问题。
细节是魔鬼。