二分查找算法及其实例
问题一:二分查找
- 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9 输出: 4 解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2 输出: -1 解释: 2 不存在 nums 中因此返回 -1
提示:
你可以假设 nums 中的所有元素是不重复的。 n 将在 [1, 10000]之间。 nums 的每个元素都将在 [-9999,9999]之间。
解题思路
- 二分查找算法是通过不断缩小需要查找的目标区域,直到找到目标值,或者区域为空,结束循环
- 具体来讲,先要找到左右边界然后
(Right + Left) / 2
即为当前查找区域的中点,通过判断中点值与目标target
的大小关系,选择缩小哪一边的边界,每次至少能够排除当前区域的一半,这里需要注意左右边界在更新时不是直接等于 mid 而是等于 mid±1
,否则会造成当区域大小为2时死循环 - 以下是C++代码实现
class Solution {
public:
int search(vector<int>& nums, int target) {
int Left = 0;
int Right = nums.size()-1;
while (Left <= Right)
{
int mid = (Right + Left) / 2;
if (nums[mid] > target)
{
Right = mid - 1;
}
else if (nums[mid] < target)
{
Left = mid + 1;
}
else
{
return mid;
}
}
return -1;
}
};
问题二:第一个错误的版本
- 你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
- 假设你有 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提示:
1 <= bad <= n <= 2^31 - 1
解题思路
- 在二分查找的基础上需要调整目标值的判断方式,在此问题中所求目标值是没有唯一的参照物的,所以不能单纯i通过isBadVersion接口返回的bool值判断是否是首个错误的版本,而是需要多判断一次
mid+1
时的情况,通过两次判断
锁定首个错误的版本 - 具体来说就是当
isBadVersion(mid)
返回为false
时表明mid的左边全是好的版本,那么范围就缩小到mid的右边了,如果此时isBadVersion(mid+1)
返回为true
这表示就在mid+1的位置出现了第一个错误的版本
// The API isBadVersion is defined for you.
bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
long left = 1;
long right = n;
while(left<=right)
{
long mid = (left+right)/2;
if(isBadVersion(mid) == true)
{
right = mid-1;
}
else if(isBadVersion(mid) == false)
{
//当二分区域的mid值不是错误版本时,证明首个错误版本就在(mid,right]之间,因此判断mid+1是否时首个错误版本
if(isBadVersion(mid+1) == true)
{
return mid + 1;
}
else
{
left = mid + 1;
}
}
}
return 1;
}
};
问题三:搜索插入位置
- 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5 输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2 输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7 输出: 4
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
-104<= target <= 104
nums 为 无重复元素 的 升序 排列数组
解题思路
- 此题思路与第一题类似但是多出了一种情况,
就是当值不在数组中时需要返回需要插入的位置
,因此我们在每次判断mid大于或者小于目标值后,还需要注意其相邻值
是否小于或大于目标值,这里的相邻指的是在当前查找范围内的一侧的相邻值
,如果一方大于另一方小于则表明此数在数组中不存在,插入位置就是当前的mid或者mid+1的位置 - 若查找范围缩小为空仍然没找到对应位置,那么根据现在left或right的所在位置判断,如果left超过数组最右侧,则表示需要插入的元素在最右边,否则在最左边插入
class Solution {
public:
int searchInsert(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)
{
return mid;
}
else if(nums[mid] > target)
{
if(mid-1>=0 && nums[mid-1] < target)
{
return mid;
}
right = mid - 1;
}
else
{
if(mid+1<=nums.size()-1 && nums[mid + 1] > target)
{
return mid + 1;
}
left = mid + 1;
}
}
if(left>nums.size()-1)
{
return nums.size();
}
else
{
return 0;
}
}
};