半年前之前写过一道二分查找的题,
学的时候老师说是有序才可用,做题的时候题解说有二段性就可以用,当时是真的苦恼,难受好几天,今天终于搞懂了,
看到以前写的二分查找博客深感实在是low,什么都没讲清楚,
遂今天重写一篇,希望可以帮助到当时和我一样的同学。
普通版二分查找:
普通版的二分查找是比较简单的,我们在学习C语言时应该都接触过。
但是我们还是要引一下。
二分查找题目
暴力循环:
暴力就很简单,直接遍历一遍,这样的时间复杂度是O(N)
。
二分查找:
暴力的效率低的原因是没有利用单调性
,因此我们要加以利用,
当我们选择中间的数时,
中间的数与target
进行比较就可以得到两段区间,
得到一段小于target的值,另一段大于target,
这也就是我们常说的二段性
,根据规则可以划分为两段。
这时可能会有同学有疑问,我选择3/1的位置可以嘛或者…
答案都是可以的,但是我们还是要选择中间的数(效率最高),因为这是通过数学的方法严格证明出来的,数学期望什么的
明白二段性后我们就可以根据你的需要进而选择哪左段还是右段。
然后一直循环,直到最后得到结果或得到这个数是否存在
话不多少直接进入代码部分。
代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size()-1;
while(left <= right)
{
int mid = (right - left) / 2 + left;
if(nums[mid] < target)
left = mid + 1;
else if(nums[mid] > target)
right = mid - 1;
else
return mid;
}
return -1;
}
};
关于这段代码还是有两个比较细节的点。
- 关于循环判断
我们要使用left <= right,为什么不用left < right呢
- 取
mid
时防止溢出,关于mid的取法有两种:
(right - left) + left
或者(right - left + 1) + left
在普通二分中这两种取mid都是可以的
引子:
在排序数组中查找元素的第一个和最后一个位置。
由此题我们引出进阶版本
普通版本的二分解决这种重复的有序数组时间复杂度是比较高的。
我们由此引出可以解决这种情况的二分查找。
进阶版:
也就是万能二分!
进阶版其实与普通版相比也只是多了点细节而已。
我们先来看求左端时的情况。
左端:
先来看最重要的部分。
普通版的判断是
if(nums[mid] < target)
left = mid + 1;
else if(nums[mid] > target)
right = mid - 1;
else
return mid;
而求左端的是:
if(nums[mid] < target)
left = mid + 1;
else if(nums[mid] >= target)
right = mid
为什么呢?
好了,最重要的地方就结束了。
什么?这就结束了?
最重要的结束了,但是最细节的地方我们还没开始。
有两个细节部分
- 关于
while
内的判断部分
我们选择使用
left < right
原因有二
- 我们相等时不需要进while循环内
这里我们举3个例子
- 在某种情况下会死循环
- 关于取中的的取法
在取左端时,我们选择(right - left) + left
,原因在于如果我们选择(right - left + 1) + left
,则会造成死循环。
模版总结:
while(left < right)
{
int mid = (right - left) + left;
if(nums[mid] < target)
left = mid + 1;
else if(nums[mid] >= target)
right = mid;
}
右端:
右端与左端只有一个细节区别(取中),推理思路也一样,故不详细解释,直接给出模版
while(left < right)
{
int mid = (right - left + 1) + left;
if(nums[mid] <= target)
left = mid;
else if(nums[mid] > target)
right = mid - 1;
}
引子的代码实现:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> ans;
if(nums.size() == 0)
{
ans.push_back(-1);
ans.push_back(-1);
return ans;
}
int left = 0, right = nums.size()-1;
//求左端
while(left < right)
{
int mid = left + (right - left) / 2;
if(nums[mid] < target) left = mid + 1;
else if(nums[mid] >= target) right = mid;
}
if(nums[left] == target) ans.push_back(left);
else
{
ans.push_back(-1);
ans.push_back(-1);
return ans;
}
//求右端
left = 0, right = nums.size()-1;
while(left < right)
{
int mid = left + (right - left + 1) / 2;
if(nums[mid] <= target) left = mid;
else if(nums[mid] >= target) right = mid - 1;
}
if(nums[left] == target) ans.push_back(left);
return ans;
}
};