二分查找
介绍
二分查找,也叫折半搜索、对数搜索。是用来在一个有序
数组中查找一个数的算法。
例题
题目描述
给定一个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
提示:
- 你可以假设 n u m s nums nums中的所有元素是不重复的。
- n n n将在 [ 1 , 10000 ] [1, 10000] [1,10000]之间。
- n u m s nums nums的每个元素都将在 [ − 9999 , 9999 ] [-9999, 9999] [−9999,9999]之间。
解题思路
思路1:暴力 ( O ( n ) ) (O(n)) (O(n))
遍历数组,如果出现了target
值则返回其下标,如果遍历完还没有出现target
值则返回-1,因为整个数组需要遍历一遍,因此时间复杂度为
O
(
n
)
O(n)
O(n)。
代码
class Solution {
public:
int search(vector<int>& nums, int target) {
for(int i = 0; i < nums.size(); i ++) // 遍历数组
if(nums[i] == target) return i; // 如果出现了target值,返回下标
return -1; // 遍历完还没出现target值,返回-1
}
};
思路2:二分 ( O ( l o g n ) ) (O(logn)) (O(logn))
从题目中我们发现数组是有序的,符合二分查找的使用条件。假设数组长度为n
,我们可以先看nums[n / 2]
,假如nums[n / 2] > target
,那么就意味着nums[n / 2]
之后的所有数都大于target
,我们只需要从
0
∼
n
/
2
0 \sim n / 2
0∼n/2中去找target
即可。否则我们就从
n
/
2
∼
n
−
1
n / 2 \sim n - 1
n/2∼n−1中去找,当只剩下最后一个元素时,我们判断它与target是否相等,如果相等就返回下标,否则返回-1.因为我们每次都只会用到前一半或者后一半的数组,最后数组长度从n
变成1
,因此总共操作次数k
符合
2
k
=
n
2^k = n
2k=n,因此k = logn
,时间复杂度为
O
(
l
o
g
n
)
O(logn)
O(logn)。
代码
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0,r = nums.size() - 1; // 设l为左边界,r为右边界,从nums[]l~r]中查找target
while(l < r){ // 如果还剩下不止一个元素,则继续查找
int mid = l + r >> 1; // 求出l~r的中点
if(nums[mid] < target) l = mid + 1; // 假如中点值小于target,,就从nums[mid + 1,r]中查找target
else r = mid; // 否则从nums[l,mid]中查找target
}
if(nums[l] == target) return l; // 如果剩下的数 == target,返回下标
return -1;// 否则返回-1
}
};
二分思想
前面的例题应该已经让你对二分有个初步的概念。就是将一个有序数组分成两半,其中一半的元素都 < = m i d <=mid <=mid值,另外一半反之,因此只有一半是我们可能需要的,另一半我们可以直接舍弃。从广义上看,不仅是排序好的数组可以用二分,只要是左右两边具有不同的性质都可以用二分,排序好的数组中,mid左边的数都<=mid,mid右边的数都>=mid,这便是左右两边的不同性质,可以让我们对数组进行折半拆分。,例如搜索旋转排序数组,这个题目的数组虽然没有严格排序,但它依然可以用二分解答。
边界问题
整数二分有一个边界问题,那就是在if(nums[mid] < target) l = mid + 1;
中能不能改成l = mid
,答案是不可以,假如我们有一个从
1
∼
100
1 \sim 100
1∼100内猜数字target = 7
的程序代码如下:
#include <iostream>
using namespace std;
int main(){
int n =100;
int target = 7;
int l = 1,r = 100;
while(l < r){
cout << l << ' ' << r << endl; // 输出l 和 r
int mid = l + r >> 1;
if(mid < target) l = mid;
else r = mid;
}
cout << l;
return 0;
}
我们很容易发现程序陷入了死循环,因为当l = 6,r = 7
时,,mid = l + r >> 1 = 6
,6 < 7
,因此l = mid = 6
,因此陷入了死循环,归根结底是因为mid = l + r >> 1
是向下取整的,因此当还剩2个元素的时候l和mid有可能相等,因此令l = mid
会引发死循环,所以l = mid + 1
在这份代码中是无法修改为l = mid
的,其它情况也可以自由探索。