二分查找
二分查找是一种基础算法,通常条件是待查找序列是有序的,从有序序列中查找便可以不用逐个元素进行比较。所谓二分查找仅是不断比较中间的元素缩小待查找的范围,这样就可是使得查找的平均时间复杂度降至 O ( l o g N ) O({log}N) O(logN)。当然在一般的题目中不会直接让写二分查找,当然他的基础代码还是需要牢记和理解。
int binarySearch(vector<int> nums, target){
int left = 0, right = nums.size()-1;
while(left <= right){
int mid = left+(right-left)/2;
if(nums[mid] == target)return mid;
else if(nums[mid] > target)right = mid-1;
else left = mid+1;
}
return -1;
}
题目1: leetcode 34
描述:给定一个按照升序排列的整数数组 nums
,和一个目标值 target
。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]
。
输入输出示例:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
思路1: 暴力搜索
很直观我们可以直接从左往右搜索最左边第一个等于target
的值的索引leftIndex
,然后从右往左搜索第一个等于target
的值的索引rightIndex
,最后返回[leftIndex, rightIndex]
。实现也十分简单:
vector<int> searchRange(vector<int>& nums, int target) {
int leftIndex=-1, rightIndex=-1, n=nums.size();
for(int i=0; i<n; i++){
if(nums[i] == target){
leftIndex = i;
break;
}
}
for(int i=n-1; i>=0; i--){
if(nums[i] == target){
rightIndex = i;
break;
}
}
return {leftIndex, rightIndex};
}
我们可以看到这里时间复杂度其实是
O
(
n
)
O(n)
O(n),因为他是直接对nums
数组做一个循环来实现搜索,而空间复杂度只用到了常数空间,所以为
O
(
1
)
O(1)
O(1)。
思路2: 二分查找
想到二分查找也是十分容易的,因为题目中给出的nums
是有序的,在一个有序的序列中查找当然是要选择复杂度更低的二分查找。但是最原始的二分查找找的是在序列中的索引,这个题目中的要找的是一个等于target
数值的一个索引范围,所以直接上二分查找不能得到最终的结果,因此需要做一个小转换,去寻找第一个大于target
的下标,具体实现如下:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int leftIndex = binarySearch(nums, target-1);
int rightIndex = binarySearch(nums, target)-1;
if(leftIndex<=rightIndex && nums[leftIndex]==nums[rightIndex]){
return {leftIndex, rightIndex};
}
return {-1, -1};
}
private:
int binarySearch(vector<int>& nums, int target){
int left=0, right=nums.size()-1, ans=nums.size();
while(left <= right){
int mid = left+(right-left)/2;
if(nums[mid] > target){
right = mid-1;
ans = mid;
}
else{
left = mid+1;
}
}
return ans;
}
};
时间复杂度: O ( l o g N ) O(logN) O(logN),空间复杂度 O ( 1 ) O(1) O(1)。
题目2:leetcode33
题目描述: 整数数组 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
。
输入输出示例:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
思路1:找旋转
直观想法,我们只要找到了在哪个地方旋转了,然后再根据target
和nums[0]
判断要寻找的数在哪一段,最后二分查找。第一步找到什么地方做了旋转,这里同样可以通过二分法,试想从左右两边开始查找,当中间值nums[mid]>nums[right]
时,说明被旋转的点在mid
右边,所以left=mid+1
。当中间值nums[mid]<nums[right]
时,说明被旋转点在mid
左边,所以right=mid-1
。有了被旋转的位置,那就是当target>nums[0]
时,target
在旋转值的左段,否则在旋转值的右段,然后利用二分查找即可获得要查找的位置。实现一下:
class Solution {
public:
int search(vector<int>& nums, int target) {
int pivot = searchPivot(nums);
if(nums[0]>target || pivot==0){
return binarySearch(nums, target, pivot, nums.size()-1);
}
return binarySearch(nums, target, 0, pivot);
}
private:
// pivot < nums[right],则最小值在左半边,收缩右边界。
// pivot > nums[right],则最小值在右半边,收缩左边界。
int searchPivot(vector<int>& nums){
int left=0, right=nums.size()-1;
while(left < right){
int pivot = (right+left)/2;
if(nums[pivot] < nums[right]){
right = pivot;
}
else{
left = pivot+1;
}
}
return left;
}
int binarySearch(vector<int> nums, int target, int left, int right){
while(left <= right){
int mid = left+(right-left)/2;
if(target == nums[mid])return mid;
else if(target > nums[mid])left = mid+1;
else right = mid-1;
}
return -1;
}
};
思路2:直接二分查找
根据第一种思路中找旋转点的想法,我们可以根据pivot
索引的值和left
索引的值判断当前的pivot
处于哪一段,然后判断target
与nums[pivot]
的关系来更新left
和right
。
- 当
nums[pivot] >= nums[left]
时。这时如果target>nums[left]
并且target<nums[pivot]
,那说明target
在left
和pivot
之间,索引更新right=pivot-1
。否则就说明targte
出现在pivot
的右边段,更新left=povit+1
。 - 当
nums[pivot] < nums[left]
时。当target > nums[pivot]
并且target <= nums[right]
,更新left=pivot+
。否则说明target
在pivot
左边段,更新right=pivot-1
。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0, right=nums.size()-1;
while(left <= right){
int pivot = left+(right-left)/2;
if(nums[pivot] == target)return pivot;
if(nums[pivot] >= nums[left]){
if(nums[left]<=target && target<nums[pivot]){
right = pivot-1;
}
else{
left = pivot+1;
}
}
else{
if(nums[pivot]<target && target<=nums[right]){
left = pivot+1;
}
else{
right = pivot-1;
}
}
}
return -1;
}
};