算法---二分算法

本文详细介绍了二分查找算法,包括其定义、基本思想、优缺点和应用场景。重点讨论了寻找目标值、左侧边界和右侧边界的不同实现,并分析了算法细节,如搜索区间的处理和循环终止条件。此外,还提供了不同场景下的二分查找模板和相关问题的解答,如在旋转数组和二维矩阵中的应用。
摘要由CSDN通过智能技术生成

一:二分法算法分析
1、二分查找算法定义
二分查找又称折半查找,它是一种效率较高的查找方法。

二分查找要求:线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构。

2、基本思想
(1)首先确定该区间的中点位置

(2)将待查的K值与R[mid]比较:若相等,则查找成功并返回此位置,否则须确定新的查找区间,继续二分查找

(3) ① 若R[mid].key>K,将查找区间变为**[left,mid-1]**

​ ②若R[mid].key<K,将查找区间变为**[mid+1,right]**

3、优缺点
③ 二分查找的优点

折半查找的时间复杂度为O(logn),远远好于顺序查找的O(n)。

④ 二分查找的缺点

虽然二分查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算。既使采用高效率的排序方法也要花费O(nlgn)的时间。

4、二分查找常用场景
寻找一个数、寻找左侧边界、寻找右侧边界。

细节:

​ while循环中的不等号是否应该带等号,mid 是否应该加一等等。

5、二分查找常用框架
int binarySearch(int[] nums, int target) {
int left = 0, right = …;

while(...) {
    int mid = left + (right - left) / 2;
    if (nums[mid] == target) {
        ...
    } else if (nums[mid] < target) {
        left = ...
    } else if (nums[mid] > target) {
        right = ...
    }
}
return ...;

}

分析二分查找的一个技巧是:不要出现 else,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节

计算 mid 时需要技巧防止溢出,建议写成: mid = left + (right - left) / 2

二.基本寻找与左右边界
1.寻找一个数(最基本)
搜索一个数,如果存在,返回其索引,否则返回 -1。

int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 注意

while(left <= right) { // 注意
    int mid = (right + left) / 2;
    if(nums[mid] == target)
        return mid; 
    else if (nums[mid] < target)
        left = mid + 1; // 注意
    else if (nums[mid] > target)
        right = mid - 1; // 注意
    }
return -1;

}
注意点(细节)分析

  1. 为什么 while 循环的条件中是 <=,而不是 < ?

答:因为初始化 right 的赋值是 nums.length - 1,即最后一个元素的索引,而不是 nums.length。

这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。

我们这个算法中使用的是 [left, right] 两端都闭的区间。这个区间就是每次进行搜索的区间,我们不妨称为「搜索区间」(earch space)。

什么时候应该停止搜索呢?当然,找到了目标值的时候可以终止:

if(nums[mid] == target)
    return mid; 

但如果没找到,就需要 while 循环终止,然后返回 -1。那 while 循环什么时候应该终止?搜索区间为空的时候应该终止,意味着你没得找了,就等于没找到嘛。

while(left <= right)的终止条件是 left == right + 1,写成区间的形式就是 [right + 1, right],或者带个具体的数字进去 [3, 2],可见这时候搜索区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。

while(left < right)的终止条件是 left == right,写成区间的形式就是 [right, right],或者带个具体的数字进去 [2, 2],这时候搜索区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就可能出现错误。

当然,如果你非要用 while(left < right) 也可以,我们已经知道了出错的原因,就打个补丁好了:

//…
while(left < right) {
// …
}
return nums[left] == target ? left : -1;

  1. 为什么 left = mid + 1,right = mid - 1?我看有的代码是 right = mid 或者 left = mid,没有这些加加减减,到底怎么回事,怎么判断?

答:刚才明确了「搜索区间」这个概念,而且本算法的搜索区间是两端都闭的,即 [left, right]。那么当我们发现索引 mid 不是要找的 target 时,如何确定下一步的搜索区间呢?

当然是去搜索 [left, mid - 1] 或者 [mid + 1, right] 对不对?因为 mid 已经搜索过,应该从搜索区间中去除。

  1. 此算法有什么缺陷?

答:至此,你应该已经掌握了该算法的所有细节,以及这样处理的原因。但是,这个算法存在局限性。

比如说给你有序数组 nums = [1,2,2,2,3],target = 2,此算法返回的索引是 2,没错。但是如果我想得到 target 的左侧边界,即索引 1,或者我想得到 target 的右侧边界,即索引 3,这样的话此算法是无法处理的。

这样的需求很常见。你也许会说,找到一个 target 索引,然后向左或向右线性搜索不行吗?可以,但是不好,因为这样难以保证二分查找对数级的时间复杂度了。

我们后续的算法就来讨论这两种二分查找的算法。

2、寻找左侧边界的二分搜索
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length; // 注意

while (left < right) { // 注意
    int mid = (left + right) / 2;
    if (nums[mid] == target) {
        right = mid;
    } else if (nums[mid] < target) {
        left = mid + 1;
    } else if (nums[mid] > target) {
        right = mid; // 注意
    }
}
return left;

}

注意点(细节)分析

  1. 为什么 while(left < right) 而不是 <= ?**

答:用相同的方法分析,因为初始化 right = nums.length 而不是 nums.length - 1 。因此每次循环的「搜索区间」是 [left, right) 左闭右开。

while(left < right) 终止的条件是 left == right,此时搜索区间 [left, left) 恰巧为空,所以可以正确终止。

  1. 为什么没有返回 -1 的操作࿱
  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值