二分查找以及相关问题(c++)

二分查找是基于分治的一种搜索方法。他需要原始数据是有序的,将中间值与目标值比较,进而减少一半的搜索范围,所以一般时间复杂度O(logn)。
除了经典的二分查找,本文还将介绍几种变种问题,二分查找插入点、二分查找左右边界。

二分查找

题目链接:二分查找
代码(左闭右闭):

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size();
        int l = 0;  //左右指针
        int r = n - 1;  
        while (l <= r){  //需要取等,不取等会漏掉左右两侧的值
            int mid = (r - l) / 2 + l;    
            if (nums[mid] < target){  //三种情况,缩小一般范围
                l = mid + 1;
            }else if (nums[mid] > target){
                r = mid - 1;
            }else{
                return mid;
            }
        }
        return -1;
    }
};

代码(左闭右开):

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size();
        int l = 0;
        int r = n;  //区别一:右开,所以不减一  [0,n)
        while (l < r){  //右区间取不到,所以不取等
            int mid = (r - l) / 2 + l;
            if (nums[mid] < target){
                l = mid + 1;  //左侧可以取到,所以可以+1
            }else if (nums[mid] > target){
                r = mid;   //右侧取不到,所以不能—1
            }else{
                return mid;
            }
        }
        return -1;
    }
};

注: mid = (r - l) / 2 + l;因为(r+l)/2可能越界(如超int范围)。

二分查找插入点

区别:不同于找一个值,如果有重复数字,插入点是最左侧的数字的下标;如果没有目标值,插入点是第一个大于目标值的下标。因此肯定有插入点。
1.不存在重复的元素

int binarySearchInsertionSimple(vector<int> &nums, int target) {
	int i = 0, j = nums.size() - 1; // 初始化双闭区间 [0, n-1]
	while (i <= j) {
		int m = i + (j - i) / 2; // 计算中点索引 m
		if (nums[m] < target) {
			i = m + 1; // target 在区间 [m+1, j] 中
		} else if (nums[m] > target) {
			j = m - 1; // target 在区间 [i, m-1] 中
		} else {
			return m; // 找到 target ,返回插入点 m
		}
	}
	// 未找到 target ,返回插入点 i
	return i;
}

注:循环完成后,𝑖 指向最左边的 target,𝑗 指向首个小于 target 的元素,因此索引 𝑖 就是插入点。
2. 有重复元素查找插入点
思路:如果按照无重复元素找插入点,不能确定是不是第一个。
向左顺序遍历的话时间复杂度最坏就是O(n),因此此方法不行。
我们可以改变中间值和目标值相等的代码,当 nums[m] == target 时,说明小于 target 的元素在区间 [𝑖, 𝑚 − 1] 中,因此采用 𝑗 = 𝑚 − 1 来缩小区间, 从而使指针 𝑗 向小于 target 的元素靠近。

int binarySearchInsertion(vector<int> &nums, int target) {
	int i = 0, j = nums.size() - 1; // 初始化双闭区间 [0, n-1]
	while (i <= j) {
		int m = i + (j - i) / 2; // 计算中点索引 m
		if (nums[m] < target) {
			i = m + 1; // target 在区间 [m+1, j] 中
		} else if (nums[m] > target) {
			j = m - 1; // target 在区间 [i, m-1] 中
		} else {
			j = m - 1; // 区别:首个小于 target 的元素在区间 [i, m-1] 中
		}
	}
	// 未找到 target ,返回插入点 i
	return i;
}

二分查找左右边界

区别:该问题与插入点的区别在于,如果没有这个数,需要返回-1,存在这个数就等于寻找插入点。

  1. 查找左边界
    使用查找插入点函数(返回值范围[0,n]),如果不等于目标值则说明没有找到,但是前提是不能越界,所以需要加上i==nums.size()进行判断。
int binarySearchLeftEdge(vector<int> &nums, int target) {
	// 等价于查找 target 的插入点
	int i = binarySearchInsertion(nums, target);
	// 未找到 target ,返回 -1
	if (i == nums.size() || nums[i] != target) {
		return -1;
	}
	// 找到 target ,返回索引 i
	return i;
}

2.查找右边界
查找target+1的左边界,返回值再-1。

int binarySearchRightEdge(vector<int> &nums, int target) {
	// 转化为查找最左一个 target + 1
	int i = binarySearchInsertion(nums, target + 1);
	// j 指向最右一个 target , i 指向首个大于 target 的元素
	int j = i - 1;
	// 未找到 target ,返回 -1
	if (j == -1 || nums[j] != target) {  //因为该函数返回范围[0,n],-1后范围[-1,n-1]
		return -1;
	}
	// 找到 target ,返回索引 j
	return j;
}

总结

本文简单介绍了二分查找以及几种变形问题,关键就是如何缩小查询的范围。希望大家对二分法有更深的理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值