写在前面
前段时间虽说一直保持刷题,但是由于课业繁重,经过组织上的反复权衡,决定停更一段时间。
现在正好是课业的空窗期,于是又来水博客力。
看官们觉得不错可以赏脸点个关注再走哦~
我的 github
二分查找的使用条件
二分查找的 O ( l o g n ) O(logn) O(logn) 高效必然有局限性:
这里就是前提条件:
1. 有序序列
2. 存在上下界
3. 随机访问特性
代码模板
left, right = 0, len(array) - 1
while left <= right:
mid = (left+right)/2
if array[mid] == target:
#find the target
break or return result
elif array[mid] < target:
left = mid + 1
else:
right = mid - 1
搜索旋转排序数组
- 暴力还原
我们用 O ( n ) O(n) O(n) 的复杂度进行peak的搜索,还原为有序,然后二分查找
- 二分查找还原
用 O ( l o g n ) O(logn) O(logn)的复杂度搜索peak(二分查找),然后还原为有序,再进行二分查找
class Solution {
public:
//主函数
int search(vector<int>& nums, int target) {
if(nums.size() == 0) return -1;
int peak = peakIndexInMountainArray(nums);
if(nums[peak] < target) {
return bs(nums, 0, peak, target);
}
return bs(nums, peak+1, nums.size(), target);
}
// 这边偷懒一下 用一下 leetcode 852.的代码
int peakIndexInMountainArray(vector<int>& nums) {
// bs
if(nums.size()==1) return nums[0];
long long l= 0, r = nums.size() -1;
while(l<=r) {
int mid = (l + r )/2;
if(nums[mid] < nums[mid +1]) l = mid +1;
else if(nums[mid] < nums[mid - 1]) r = mid - 1;
else return mid;
}
return -1;
}
int bs(vector<int> &nums, int low, int high, int target) {
while(low <= high) {
int mid = (low + high ) >> 1;
if(nums[mid] < target) low = mid+1;
else if(nums[mid] > target) high = mid -1 ;
// 找到重复元素的第一个元素
else {
if(mid==0|| nums[mid-1] != target) return mid;
high = mid - 1;
}
}
return -1 ;
}
};
- 正解:改造的二分查找
由于题目给出的输入是局部单调的,在某两段存在一定的单调性。
二分查找的条件:
- 单调(非严格满足)
- 边界(满足)
- 随机访问(满足)
例如:
nums = [4,5,6,7,0,1,2] , target = 0
在原味二分查找中,收敛条件是大于或者小于 mid 值
因此,这里我们需要思考的是,target
将会在哪一段单调部分,然后把所有区间罗列出来
我们依然取
mid = left + (right - left) / 2
然后,我们比较三个变量
nums[0] nums[mid] target
接下来,就罗列一下所有情况:(下面的i
可以看作 high 位)
-
nums[0] <= nums[mid]
,说明0 - mid
有序。nums[0] <= target < nums[mid]
,我们只要在 0 - mid 范围内查找,那也就是按照原味二分查找进行收敛。
-
nums[0] > nums[mid]
,说明0 - mid
存在peak。-
nums[0] > nums[mid] >= target
,此时target
在peak 后方,我们向后收敛。 -
target >= nums[0] > nums[mid]
,此时向前收敛。 -
nums[0] > target >= nums[mid]
,此时向后收敛。
-
这里peak这个变量我们就不考虑了
如此,我们就不难写出代码:(虽说“不难”,其实磨了好久的细节,感觉离散数学还给老师了)
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.size()==0) return -1;
int l = 0, h = nums.size() - 1;
// 按照两种情况二分
while(l < h) {
int mid = l + ((h-l) >> 1);
if(nums[mid] == target) return mid;
// 0 - mid 有序
if( nums[l] <= nums[mid]) {
// target 在这个范围内
if( target >= nums[l] && target < nums[mid] ) h = mid - 1;
else l = mid + 1;
}
// 0 - mid 存在peak
// nums[l] > nums[mid]
else {
// target 在后半段有序序列
if(target > nums[mid] && target <= nums[h] ) l = mid + 1;
else h = mid - 1;
}
}
return nums[l] == target ? l : -1;
}
};
最后顺便给一种简洁版解法,要理解的这个解法的话,要点在于自己画一张真值表
/*
作者:LukeLee
链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/ji-jian-solution-by-lukelee/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
class Solution {
public:
int search(vector<int>& nums, int target) {
int lo = 0, hi = nums.size() - 1;
while (lo < hi) {
int mid = (lo + hi) / 2;
if ((nums[0] > target) ^ (nums[0] > nums[mid]) ^ (target > nums[mid]))
lo = mid + 1;
else
hi = mid;
}
return lo == hi && nums[lo] == target ? lo : -1;
}
};
我们结合真值表,结合前面的解法,不难证明这种方案的正确性。(但是能想出来是真的大神)