使用二分查找法的前提:(1)数组为有序数组.
(2)数组中无重复元素.
二分的两种写法:
方法一:[left,right]
class Solution {
public:int search(vector<int>& nums, int target) {int left=0;int right=nums.size()-1;while (left <= right){int mid=(left+right)/2;if(nums[mid]>target)right=mid-1;else if(nums[mid]<target)left=mid+1;elsereturn mid;}return -1;}
};
方法二:[left,rigght)
class Solution {
public:int search(vector<int>& nums, int target) {int left=0;int right=nums.size()-1;while (left <=right){int mid=(left+right)/2;if(nums[mid]>target)right=mid;else if(nums[mid]<target)left=mid+1;elsereturn mid;}return -1;}
};
核心代码(递归)
int binsearch(int left,int right)
{
if(left<=right)
{
int mid=(left+right)/2;
if(nums[mid]==target)
return mid;
if(nums[mid]>target)
return binsearch(left,mid-1);
if(nums[mid]<target)
return binsearch(mid+1,right);
}
return 0;
}
核心代码(循环)
int f=-1;
while(left<=right)
{
int mid=(left+right)/2;
if(nums[mid]==target)
{
f=mid;
break;
}
if(target<nums[mid])
right=mid-1;
if(target>nums[mid])
left=mid+1
}
if(f==-1)
cout<<"没找到";
else
cout<<f<<endl;
以下是力扣二分法题目的常见解题思路:
704. 二分查找
- 初始化左右指针 left 和 right ,分别指向数组两端。
- 在 left <= right 的循环中,计算中间索引 mid 。
- 比较 nums[mid] 与 target ,若相等则返回 mid ;若 nums[mid] > target ,则更新 right = mid - 1 ;若 nums[mid] < target ,则更新 left = mid + 1 。
- 循环结束未找到则返回-1。
35. 搜索插入位置
- 同样以 left 和 right 初始化数组两端。
- 循环条件为 left <= right ,计算 mid 后,若 nums[mid] >= target ,则 right = mid - 1 ,否则 left = mid + 1 。
- 循环结束后, left 所指位置就是目标值应插入的位置。
34. 在排序数组中查找元素的第一个和最后一个位置
- 先通过二分法找目标值第一个位置,当 nums[mid] >= target 时,更新 right = mid ,循环结束后 left 可能是第一个位置,需再判断 nums[left] 是否为 target 。
- 找最后一个位置时,当 nums[mid] <= target 时,更新 left = mid ,循环结束后,若 nums[right] 是 target , right 就是最后一个位置。
69. x的平方根
- 令 left = 0 , right = x ,在 left <= right 循环中计算 mid 。
- 若 mid * mid <= x && (mid + 1) * (mid + 1) > x ,则 mid 就是所求平方根;若 mid * mid > x ,更新 right = mid - 1 ;否则更新 left = mid + 1 。
367. 有效的完全平方数
- 类似求平方根, left 设为1, right 设为 num 。
- 循环中计算 mid ,若 mid * mid == num ,则返回 true ;若 mid * mid > num ,更新 right = mid - 1 ,否则更新 left = mid + 1 。循环结束未找到则返回 false 。
162. 寻找峰值
- 初始化 left = 0 , right = nums.length - 1 。
- 当 left < right 时,计算 mid ,若 nums[mid] > nums[mid + 1] ,说明峰值在左半部分,更新 right = mid ;否则峰值在右半部分,更新 left = mid + 1 。
- 循环结束后, left 指向的就是一个峰值位置。
判断 right 应赋值为 mid 还是 mid - 1
关键在于二分查找的目标以及当前中间值与目标值的关系,具体可从以下几方面判断:
- 查找精确值且数组元素唯一:当目标是在有序数组中查找某个精确值,且数组元素唯一时,若 nums[mid] > target ,说明目标值在左半部分,此时应将 right 赋值为 mid - 1 。因为 nums[mid] 已经大于 target ,它不可能是目标值,所以下一轮查找范围应不包含 mid ,如704. 二分查找就是这种情况。
- 查找左边界或最大不超过目标值的元素:若要查找目标值在数组中的左边界,或者是查找数组中最大的、不超过目标值的元素时,当 nums[mid] >= target ,应将 right 赋值为 mid 。这是为了让查找范围继续向左侧收缩,且保留 mid 位置,因为 mid 位置有可能就是左边界,或者是符合条件的最大元素,如35. 搜索插入位置、34. 查找元素第一个位置就需这样处理。
- 查找右边界或最小超过目标值的元素:当任务是查找目标值的右边界,或者是要找到数组中最小的、超过目标值的元素时,若 nums[mid] <= target ,则应将 right 赋值为 mid - 1 。这样能使查找范围向右收缩,同时排除 mid 位置,因为 mid 位置及左侧元素已不满足“右边界”或“最小超过目标值”的条件,例如34. 查找元素最后一个位置时就遵循此规则。
二分法是一种通过不断将区间一分为二,逐步逼近目标值的算法方法,适用于以下几类题型:
-方程求解类:用于求解方程f(x)=0的根。若函数f(x)在区间[a,b]上连续,且f(a)\cdot f(b)<0,则可利用二分法在该区间内寻找方程的根。如求解方程x^3 - 2x - 1 = 0在区间[1,2]内的根。
- 函数最值类:当函数f(x)在某区间上具有单调性,且已知最值所在的大致区间时,可通过二分法来逼近最值。例如,求函数f(x)=x^2 - 4x + 3在区间[0,3]上的最小值,可利用二分法不断缩小包含最小值的区间。
- 数据查找类:常用于在有序数组中查找特定元素。如在一个按升序排列的整数数组 [1, 3, 5, 7, 9, 11] 中查找数字7,可使用二分法,每次将数组分成两部分,确定目标元素在左半部分还是右半部分,逐步缩小查找范围。
- 参数确定类:一些问题中需要确定某个参数的取值,使得某个条件成立。若该条件与参数之间存在单调关系,可借助二分法确定参数值。比如,在物理实验中,要确定一个电阻值,使得电路中的电流满足某个范围,已知电流与电阻成反比例关系,就可通过二分法来尝试不同电阻值,找到符合条件的电阻。
- 最优解搜索类:在一些组合优化问题中,若目标函数具有单调性或满足一定的条件,可利用二分法搜索最优解。如背包问题中,已知背包容量和物品重量价值,二分查找能装入背包的最大价值对应的重量限制,进而求解最优装包方案。