006【入门】二分搜索

本博客是观看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是否存在

数组134913152325
下标01234567
LR

首先找到数组的中点的下标 int[(0 + 7) / 2] = 3

对应的值为9,而9<15,则在下标4-7范围内继续寻找

数组13152325
下标4567
LR

找到中点下标: 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

数组369131725
下标012345
LR

中点对应的值为9, 9 > 8,记录ans=2,R来到2-1=1

数组36
下标01
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/

image.png

思路:

  • 单独检验[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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值