二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
一般的二分查找模板
int binarySearch(vector<int>& nums, int target){
if(nums.size() == 0)
return -1;
int left = 0, right = nums.size() - 1;
while(left <= right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if(nums[mid] == target){ return mid; }
else if(nums[mid] < target) { left = mid + 1; }
else { right = mid - 1; }
}
// End Condition: left > right
return -1;
}
初始条件:left = 0, right = length-1
终止:left > right
向左查找:right = mid-1
向右查找:left = mid+1循环结束后即为未找到元素
力扣278. 第一个错误的版本力扣 :x 的平方根
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:
输入:x = 4
输出:2
示例 2:输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
链接:https://leetcode-cn.com/leetbook/read/binary-search/xe9cog/
来源:力扣(LeetCode)
class Solution {
public:
int mySqrt(int x) {
if(x<2){return x;}
int low=0,high=x;
while(low<=high)
{
int mid=(low)+(high-low)/2;
//防溢出 等于(low+high)/2
if(mid==x/mid)
{
//这里mid*mid会溢出
return mid;
}else if(mid<x/mid)
{
//mid*mid小于要找的值
//往大的区间去找
low=mid+1;
}else{
//mid*mid 大于x即往小区间找
high=mid-1;
}
}
return high;
}
};
二分查找模板 II
模板 2 是二分查找的高级模板。它用于查找需要访问数组中当前索引及其直接右邻居索引的元素或条件。
int binarySearch(vector<int>& nums, int target){
if(nums.size() == 0)
return -1;
int left = 0, right = nums.size();
while(left < right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if(nums[mid] == target){ return mid; }
else if(nums[mid] < target) { left = mid + 1; }
else { right = mid; }
}
// Post-processing:
// End Condition: left == right
if(left != nums.size() && nums[left] == target) return left;
return -1;
}
关键属性
一种实现二分查找的高级方法。
查找条件需要访问元素的直接右邻居。
使用元素的右邻居来确定是否满足条件,并决定是向左还是向右。
保证查找空间在每一步中至少有 2 个元素。
需要进行后处理。 当你剩下 1 个元素时,循环 / 递归结束。 需要评估剩余元素是否符合条件。
区分语法
初始条件:left = 0, right = length
终止:left == right
向左查找:right = mid
向右查找:left = mid+1链接:https://leetcode-cn.com/leetbook/read/binary-search/xerqxt/
来源:力扣(LeetCode)
力扣278. 第一个错误的版本
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n
个版本 [1, 2, ..., n]
,你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version)
接口来判断版本号 version
是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例 1:
输入:n = 5, bad = 4
输出:4
解释:
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
示例 2: 输入:n = 1, bad = 1 输出:1 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/leetbook/read/binary-search/xe5fpe/
代码
class Solution {
public:
int firstBadVersion(int n) {
if(n==1){return 1;}
int left=1,right=n;//左临界点是1
//第一个版本开始
while(left<right)
{
int mid=(left)+(right-left)/2;
if (isBadVersion(mid)) {
right = mid;
// 答案在区间 [left, mid] 中
} else {
left = mid + 1;
// 答案在区间 [mid+1, right] 中
}
}
// 此时有 left == right,区间缩为一个点,即为答案
return left;
}
};
搜索旋转排序数组
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:
输入:nums = [1], target = 0
输出:-1
进阶:你可以设计一个时间复杂度为 O(log n) 的解决方案吗?
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)
{
return mid;
}
if(nums[mid]>nums[right]){
if(nums[mid]>target&&target>=nums[left]){
right=mid-1;
}
else{
left=mid+1;
}
}
else{
if(nums[mid]<target&&target<=nums[right]){
left=mid+1;
}
else{
right=mid-1;
}
}
}
return -1;
}
};
这一道题不是很理解透
寻找峰值
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞ 。
你必须实现时间复杂度为 O(log n) 的算法来解决此问题。
示例 1:
输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5
解释:你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6
链接:https://leetcode-cn.com/leetbook/read/binary-search/xem7js/
来源:力扣(LeetCode)
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int left=0,right=nums.size()-1;
while(left<right)
{
int mid=left+(right-left)/2;
if(nums[mid]<nums[mid+1])
{
left=mid+1;
}else{
right=mid;
}
}
return left;
}
};
总结:主要思想是折半 每次用中位值去和搜索值比较 时间复杂度为 O(log n)
如果中位值比搜索值小 说明搜索值在右边 即区间[mid+1,right]
如果中位值比搜索值大 说明搜索值在左边 即区间[left,mid-1]
因为mid等于搜索值就是找到 所有要注意区间
结束条件是 找到搜索值 或者 left>right(没有搜索值)
二分查找的进阶应用有点难理解 还是得多刷题多看。