本博客是观看b站左程云的课程所做的笔记,如需观看原视频请至b站搜索观看
前置知识:无
建议:1)会的同学可以跳过,2、3、4)不要跳过
1)在有序数组中确定num存在还是不存在
2)在有序数组中找>=num的最左位置
3)在有序数组中找<=num的最右位置
4)二分搜索不一定发生在有序数组上(比如寻找峰值问题)
5)“二分答案法”这个非常重要的算法,很秀很厉害,将在【必备】课程里继续
如果数组长度为n,那么二分搜索搜索次数是logn次,以2为底
下节课讲时间复杂度,二分搜索时间复杂度0(logn)
1. 在有序数组中确定num存在还是不存在
例如:在[1,3,4,9,13,15,23,25]中确定15是否存在
数组 | 1 | 3 | 4 | 9 | 13 | 15 | 23 | 25 |
---|---|---|---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
L | 中 | R |
首先找到数组的中点的下标 int[(0 + 7) / 2] = 3
对应的值为9,而9<15,则在下标4-7范围内继续寻找
数组 | 13 | 15 | 23 | 25 |
---|---|---|---|---|
下标 | 4 | 5 | 6 | 7 |
L | 中 | R |
找到中点下标: int[(4+7)/2] = 5
对应的值为15,找到了,返回true
如果二分到最后L<R时都没有找到,则返回false
比如说依然是原数组,此时找14
找到中点下标: int[(4+7)/2] = 5
对应的值为15,而15>14,则在4-4范围内寻找
数组 | 13 |
---|---|
下标 | 4 |
L,R |
找到中点下标:int[(4+4)/2] =2
对应的值为13,而13<14,L移动到5,此时L>R,终止,返回false
Java
public static boolean exist(int[] arr, int num){
if (arr == null || arr.length == 0) return false;
int l = 0, r = arr.length - 1, m = 0;
while (l <= r){
// 这里等价于(l+r)/2,但是l + r可能会有溢出的风险,这里选用分解来避免
// 正数右移一位等价于/2,位运算速度较快
m = l + ((r - l) >> 1);
if (arr[m] == num){
return true;
}else if (arr[m] < num) {
l = m + 1;
}else{
r = m - 1;
}
}
return false;
}
python
def exist(arr, num):
if arr is None or len(arr) == 0:
return False
l, r , m = 0, len(arr) - 1, 0
while l <= r:
# 右移一位等同于 // 2
# 这里等同于 (l + r) / 2, 但是l + r会有溢出的风险,这里选用分解的方法来避免
m = l + ((r - l) >> 1)
if arr[m] == num:
return True
elif arr[m] < num:
l = m + 1
else:
r = m -1
return False
C
bool exist(int* arr, int length, int num) {
if (length == 0) return false;
int l = 0, r = length - 1, m = 0;
while (l <= r) {
// 这里等价于(l+r)/2,但是l + r可能会有溢出的风险,这里选用分解来避免
// 正数右移一位等价于/2,位运算速度较快
m = l + ((r - l) >> 1);
if (arr[m] == num){
return true;
}
else if (arr[m] < num) {
l = m + 1;
}
else {
r = m - 1;
}
}
return false;
}
C++
bool exist(vector<int>& arr, int num) {
if (arr.size() == 0) return false;
int l = 0, r = arr.size() - 1, m = 0;
while (l <= r) {
// 等价于(l + r) / 2, 由于l+r可能会存在溢出的问题,这里选择分解
// 正数右移一位等价于/2,位运算速度较快
m = l + ((r - l) >> 1);
if (arr[m] == num) {
return true;
}
else if (arr[m] < num) {
l = m + 1;
}
else {
r = m - 1;
}
}
return false;
}
2. 在有序数组中找>=num的最左位置
比如数组[3, 6, 9, 13, 17, 25] 找大于等于8的最左位置,观察可知应该返回下标2
初始化ans=-1
数组 | 3 | 6 | 9 | 13 | 17 | 25 |
---|---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 | 5 |
L | 中 | R |
中点对应的值为9, 9 > 8,记录ans=2,R来到2-1=1
数组 | 3 | 6 |
---|---|---|
下标 | 0 | 1 |
L,中 | R |
中点对应的值为3,3<9,不记录ans,L来到0+1 =1
数组 | 6 |
---|---|
下标 | 1 |
L中R |
中点对应的值为6,6<9,不记录ans,L来到1+1 =2,越界,停止,返回ans
思路:中点>=num,记答案,向左二分;中点<num,不记答案,向右二分
java
public static int findLeft(int[] arr, int num){
int l = 0, r = arr.length - 1, m = 0;
int ans = -1;
while (l <= r){
m = l + ((r - l) >> 1);
if (arr[m] >= num){
ans = m;
r = m - 1;
}else{
l = m + 1;
}
}
return ans;
}
python
def findLeft(arr, num):
l, r, m, ans = 0, len(arr) - 1, 0, -1
while l<=r:
m = l + ((r - l) >> 1)
if arr[m] >= num:
# 符合条件,继续向左寻找
ans = m
r = m - 1
else:
# 不符合条件,向右寻找
l = m + 1
return ans
C
int findLeft(int* arr, int length, int num) {
int l = 0, r = length - 1, m = 0;
int ans = -1;
while (l <= r) {
m = l + ((r - l) >> 1);
if (arr[m] >= num) {
// 满足条件,记录小标,继续向左二分,
ans = m;
r = m - 1;
}
else {
// 不满足,向右继续
l = m + 1;
}
}
return ans;
}
C++
int findLeft(vector<int>& arr, int num) {
int l = 0, r = arr.size() - 1, m = 0;
int ans = -1;
while (l <= r) {
m = l + ((r - l) >> 1);
if (arr[m] >= num) {
// 满足条件,记录小标,继续向左二分,
ans = m;
r = m - 1;
}
else {
// 不满足,向右继续
l = m + 1;
}
}
return ans;
}
3. 在有序数组中找<=num的最右位置
与2原理相同
思路:中点<=num,记答案,向右二分;中点<num,不记答案,向左二分
java
public static int findRight(int[] arr, int num) {
int l = 0, r = arr.length - 1, m = 0;
int ans = -1;
while (l <= r) {
m = l + ((r - l) >> 1);
if (arr[m] <= num) {
// 满足条件,记录下标,向右二分
ans = m;
l = m + 1;
} else {
// 不满足条件,向左二分
r = m - 1;
}
}
return ans;
}
python
def findRight(arr, num):
l, r, m, ans = 0, len(arr) - 1, 0, -1
while l<=r:
m = l + ((r - l) >> 1)
if arr[m] <= num:
# 符合条件,继续向右寻找
ans = m
l = m + 1
else:
# 不符合条件,向左寻找
r = m - 1
return ans
c
int findRight(int* arr, int length, int num) {
int l = 0, r = length - 1, m = 0;
int ans = -1;
while (l <= r) {
m = l + ((r - l) >> 1);
if (arr[m] <= num) {
// 满足条件,记录小标,继续向右二分,
ans = m;
l = m + 1;
}
else {
// 不满足,向左继续
r = m - 1;
}
}
return ans;
}
C++
int findRight(vector<int> arr, int num) {
int l = 0, r = arr.size() - 1, m = 0;
int ans = -1;
while (l <= r) {
m = l + ((r - l) >> 1);
if (arr[m] <= num) {
// 满足条件,记录小标,继续向右二分,
ans = m;
l = m + 1;
}
else {
// 不满足,向左继续
r = m - 1;
}
}
return ans;
}
4. 二分搜索不一定发生在有序数组上(比如寻找峰值问题)
已知数组中各个元素都不相同,请你返回一个峰值的位置
峰值:值大于相邻的两个值
对应leetcode:https://leetcode.cn/problems/find-peak-element/description/
思路:
- 单独检验[0]位置是否为峰值
- 单独检验[n-1]位置是否为峰值
- 看中点是否为峰值
- 左边大于中点,取左侧二分
- 右边大于中点,取右侧二分
java
public static int findPeakElement(int[] arr) {
int n = arr.length;
if (n == 1) {
return 0;
}
if (arr[0] > arr[1]) {
// 左侧是否为峰值
return 0;
}
if (arr[n - 1] > arr[n - 2]) {
// 右侧是否为峰值
return n - 1;
}
int l = 1, r = n - 2, m = 0, ans = -1;
while (l <= r) {
m = l + ((r - l) >> 1);
if (arr[m] < arr[m - 1]) {
// 对左侧进行二分
r = m - 1;
} else if (arr[m] < arr[m + 1]) {
// 对右侧进行二分
l = m + 1;
} else {
ans = m;
break;
}
}
return ans;
}
python
def findPeakElement(nums):
"""
:type nums: List[int]
:rtype: int
"""
n = len(nums)
if n == 1:
return 0
if nums[0] > nums[1]:
return 0
if nums[n - 1] > nums[n -2]:
return n - 1
l, r, m, ans = 1, n- 2, 0 ,-1
while l <= r:
m = l + ((r - l)>>1)
if nums[m] < nums[m - 1]:
# 对左侧进行二分
r = m - 1
elif nums[m] < nums[m + 1]:
# 对右侧进行二分
l = m + 1
else:
ans = m
break
return ans
C
int findPeakElement(int* arr, int length) {
if (length == 1) return 0;
if (arr[0] > arr[1]) return 0;
if (arr[length - 1] > arr[length - 2]) return length - 1;
int l = 1, r = length - 2, m = 0, ans = -1;
while (l <= r) {
m = l + ((r - l) >> 1);
if (arr[m] < arr[m - 1]) {
// 在左侧二分
r = m - 1;
}
else if (arr[m] < arr[m + 1]) {
// 在右侧二分
l = m + 1;
}
else {
ans = m;
break;
}
}
return ans;
}
C++
int findPeakElement(vector<int> arr) {
if (arr.size() == 1) return 0;
if (arr[0] > arr[1]) return 0;
if (arr[arr.size() - 1] > arr[arr.size() - 2]) return arr.size() - 1;
int l = 1, r = arr.size() - 2, m = 0, ans = -1;
while (l <= r) {
m = l + ((r - l) >> 1);
if (arr[m] < arr[m - 1]) {
// 在左侧二分
r = m - 1;
}
else if (arr[m] < arr[m + 1]) {
// 在右侧二分
l = m + 1;
}
else {
ans = m;
break;
}
}
return ans;
}