二分查找
1.朴素二分查找
朴素二分查找时while内的判断条件是while(left <= right),求解mid = left + (right - left) / 2,当nums[mid] > target,说明目标值在mid的左面,right = mid - 1,反之,left = mid + 1
1.LeetCode704.二分查找
代码解析:
class Solution {
public:
int search(vector<int>& nums, int target)
{
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 - 1;
else if(nums[mid] == target)
return mid;
}
return -1;
}
};
2.左右边界的二分查找(重点)
当数据具有二段性时可以考虑使用二分查找,二分查找是最容易写出死循环的算法,一定注意left / right的变化和while中的判断条件
模板总结:1. 查找区间左端点的模板 / 2. 查找区间右端点的模板
模板总结(重点)
查看数组是否具有二段性(以中间某个数为界,左边数(不含此数)均小于此中间点数,右边界数(含此数)均大于等于此中间数)
不同点:假设nums[mid] = x,当x < target,说明目标值在mid的右边,此时与朴素二分查找一样left = mid + 1,假设 x >= target,此时说明mid已经命中target所在的区间,但是并不能确定是否位于右边界点处,此时与而朴素二分查找不同,mid可能处于右边界点处,如果right - 1,right指向会发生错误,所以right = mid,在下一次mid继续向前判断
模板: x < target -> left = mid + 1 / x >= target -> right = mid
left不断跳出不符合target的区域,right在符合target的区域内不动
循环条件
重点:决定算法是否会进入死循环的关键:while(left < right)
注意没有等号!!!三种情况: 1.当数组符合中间值的左边小,右边大时,且有等于target的值此时left = right时就是最终结果无需判断。 2.数组中的值全部大于target,right最终会移动到left位置,此时若nums[left] != target则返回-1,若判断 left == right(mid 恒= left)则陷入死循环 3.数组中的值全部小于target,left最终会移动到right的位置
求中点的操作
仅数组个数为偶数时需要考虑(可能进入死循环的条件之一!!!)
mid = left + (right - left + 1) / 2(靠左位置) 或 mid = left + (right - left) / 2(靠右位置)(依据题目要求自行进行判断)— 方法:mid落在left = mid + 1的left上 或 right = mid - 1的right上(right - left落在靠左即left的位置,不能出现left = mid的情况,同理right - left + 1落在靠右即right的位置,不能出现right = mid的情况)
总结:二分查找的三大细节:模板中对于left / right移动的处理 / 循环条件(正常情况下都是 left < right)/ 中间点mid的判断 / 解题策略:当查找区间左端点时,左边界的值均小于target,右边界的值大于等于target,反之,当查找区间右端点时,左边界的值小于等于target,右边界的值大于target(上述模板均已查找区间左端点为例,查找区间右端点要修改模板中left / right移动的条件 : x <= target -> left = mid / x >= target -> right = mid - 1)
2.LeetCode34.二分查找模板的应用
代码解析:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target)
{
if(nums.size() == 0) return {-1, -1}; // 直接返回数组的形式
vector<int> v;
int left = 0, right = nums.size() - 1;
//查找二分区间左端点模板 - left = mid + 1 / right = mid(< 和 >=)
while(left < right)
{
// mid落在left,即左端点上(left = mid + 1)
int mid = left + (right - left) / 2;
if(nums[mid] < target)
left = mid + 1;
if(nums[mid] >= target)
right = mid;
}
if(nums[left] == target) v.push_back(left);
else v.push_back(-1);
//查找二分区间右端点 - left = mid / right = mid - 1(<= 和 >)
left = 0, right = nums.size() - 1;
while(left < right)
{
// mid落在right,即右端点上(right = mid - 1)
int mid = left + (right - left + 1) / 2;
if(nums[mid] <= target)
left = mid;
if(nums[mid] > target)
right = mid - 1;
}
if(nums[left] == target) v.push_back(left);
else v.push_back(-1);
return v;
}
};
总结:查看数组是否具有二段性(不一定有序),依据题意改变二分查找的判断条件
3.题目练习链接
LeetCode35.搜素插入位置
LeetCode69.x的平方根
LeetCode852.山脉数组的峰顶索引
LeetCode162.寻找峰值
LeetCode153.寻找旋转排序数组中的最小值
总结:二分查找是一种时间复杂度为O(logN)的算法,可以解决一些需多次遍历数组的题目,技巧方面,若下面right出现mid - 1,则上面要right - left + 1,即下面出现-1,上面就+1,一句题目分析,就题论题即可
End!!!