[TOC]
数组可以快速使用二分查找法的条件:
- 数组为有序数组(必须具备,否则换思路)
- 数组无重复元素(最好具备,否则查找的index无法确定是哪一个target)
一、二分查找含重复元素的数组
34. 在排序数组中查找元素的第一个和最后一个位置(中等难度)
class Solution {
public:
/*解析:本题是在一个排序数组中查找,首选二分查找法。
本题的难点在于数组中存在重复元素,传统的二分查找法可以找到其中某一个target,但不能判断是哪一个
可以巧妙地在传统二分查找得到的index上试着左移或右移来找到边界。
*/
vector<int> searchRange(vector<int>& nums, int target) {
int index=binarySearch(nums,target);//尝试二分查找
if(index==-1) return {-1, -1};// 数组中不存在target
// 此时数组中存在target,但是不确定有几个target,index指向其中的一个target
// 寻找左边界,尝试多次移动左边界
int leftBorder=index;
while(leftBorder-1>=0&&nums[leftBorder-1]==target)leftBorder--;
// 寻找右边界,尝试多次移动右边界
int rightBorder=index;
while(rightBorder+1<nums.size()&&nums[rightBorder+1]==target)rightBorder++;
return {leftBorder, rightBorder};
}
private:
// 二分查找函数[left,right]
int binarySearch(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1;
int res = nums.size(); // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int mid = left + ((right - left) / 2);//等价于(left + right)/2,防止溢出
if(nums[mid]==target) return mid;
if(nums[mid]>target) right = mid - 1;
else left = mid + 1;
}
return -1;
}
};
剑指 Offer 53 - I. 在排序数组中查找数字 I(简单难度)
class Solution {
public:
/*解析:本题是在一个排序数组中查找,首选二分查找法。
本题的难点在于数组中存在重复元素,传统的二分查找法可以找到其中某一个target,但不能判断是哪一个
可以巧妙地在传统二分查找得到的index上试着左移或右移来找到边界。
*/
int search(vector<int>& nums, int target) {
int index=binarySearch(nums,target);//尝试二分查找
if(index==-1) return 0;// 数组中不存在target
// 此时数组中存在target,但是不确定有几个target,index指向其中的一个target
// 寻找左边界,尝试多次移动左边界
int leftBorder=index;
while(leftBorder-1>=0&&nums[leftBorder-1]==target)leftBorder--;
// 寻找右边界,尝试多次移动右边界
int rightBorder=index;
while(rightBorder+1<nums.size()&&nums[rightBorder+1]==target)rightBorder++;
return rightBorder-leftBorder+1;
}
// 二分查找函数[left,right]
int binarySearch(vector<int>& nums, int target) {
int left = 0;
int right = nums.size()-1;
int res = nums.size(); // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int mid = left + ((right - left) / 2);//等价于(left + right)/2,防止溢出
if(nums[mid]==target) return mid;
if(nums[mid]>target) right = mid - 1;
else left = mid + 1;
}
return -1;
}
};
二、二分查找旋转排序数组中的最小值
153. 寻找旋转排序数组中的最小值(中等难度)
154. 寻找旋转排序数组中的最小值 II(困难难度)
剑指 Offer 11. 旋转数组的最小数字(简单难度)
class Solution {
public:
/*本题是求一个有重复元素的旋转排序数组中的最小值,
实际上无论是否旋转,数组的最小值都是不变的,所以通过这道题很简单。
由于限定算法的时间复杂度为O(logn),所以必须使用二分查找法。
假设数组最后1个元素为nums[right],
则最小值的右侧元素都<=nums[right];最小值的左侧元素都>=nums[right];
我们的nums[mid]可以和nums[right]比较来进行二分查找
有没有重复元素会影响nums[mid]==nums[right]的情况:
没有重复元素时,mid一定就是right,right==mid即可,当然right-=1也对。
有重复元素时,mid可能在最小值右侧或者左侧,索性采取保守策略,right统一左移1个元素。
*/
int findMin(vector<int>& nums) {
int left = 0;// 左指针
int right = nums.size() - 1;//右指针
// 循环遍历[left,right]
while (left < right) {
int mid = left + (right - left) / 2;//折半操作
// 如果nums[mid]==nums[right],说明mid就是右指针,右指针左移1次
if(nums[mid]==nums[right]) right -= 1;//无重复元素时right = mid也可以
// 如果nums[mid]<nums[right],说明mid在最小值右侧,移动右指针
else if (nums[mid] < nums[right]) right = mid;
// 如果nums[mid]>nums[right],说明mid在最小值左侧,移动左指针
else left = mid + 1;
}
// 遍历结束时,left==right,指向同一个元素,就是最小值
return nums[left];
}
};
三、二分查找旋转排序数组中的某个值
33. 搜索旋转排序数组(中等难度)
class Solution {
public:
/* 本题的题意中提到了排序以及时间复杂度为O(log n),所以在暗示你使用二分查找法。
本题是在一个局部有序的数组中查找某个值,原先升序数组经过旋转变成了局部有序,增加了复杂度
本题的旋转数组有两个升序区间。
二分查找法最重要的明白nums[mid]和谁比较,本题整体是nums[mid]和target比较,存在三种情况:
nums[mid]和nums[0]的比较结果会影响上述三种情况的判断。
*/
int search(vector<int>& nums, int target) {
int n = nums.size();
if (n==0) return -1;
if (n==1) return nums[0] == target ? 0 : -1;
int left = 0;// 左指针
int right = n - 1;// 右指针
// 二分查找[left,right]
while (left <= right) {
int mid = right+(left-right) / 2;
// 如果nums[mid] == target,已找到,直接返回
if (nums[mid] == target) return mid;
// 如果nums[mid] >=nums[0],说明mid在第一段升序区间中,直接返回
if ( nums[mid]>=nums[0] ) {
if (nums[0] <= target && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
// 如果nums[mid] <nums[0],说明mid在第二段升序区间中,
else {
// 如果nums[mid]< target并且target没有越界,移动左指针
if (nums[mid] < target && target <= nums[n - 1]) {
left = mid + 1;
}
// 如果nums[mid]>= target,移动右指针
else {
right = mid - 1;
}
}
}
return -1;
}
};
81. 搜索旋转排序数组 II(中等难度)
class Solution {
public:
bool search(vector<int>& nums, int target) {
int n = nums.size();
if (n==0) return false;
if (n==1) return nums[0] == target;
int left = 0;// 左指针
int right = n - 1;// 右指针
// 二分查找[left,right]
while (left <= right) {
int mid = right+(left-right) / 2;
// 如果nums[mid] == target,已找到,直接返回
if (nums[mid] == target) return true;
if(nums[left] == nums[mid] && nums[mid] == nums[right]){
left++;
right--;
}
// 如果nums[mid] >=nums[0],说明mid在第一段升序区间中,直接返回
else if ( nums[mid]>=nums[left] ) {
if (nums[left] <=target && target < nums[mid]) right = mid - 1;
else left = mid + 1;
}
// 如果nums[mid] <nums[0],说明mid在第二段升序区间中,
else {
// 如果nums[mid]< target并且target没有越界,移动左指针
if (nums[mid] < target && target <= nums[n - 1]) left = mid + 1;
// 如果nums[mid]>= target,移动右指针
else right = mid - 1;
}
}
return false;
}
};