一文带你搞定二分查找及其多个变种!

在这里插入图片描述

1 完全有序

二分查找

二分查找也称折半查找(Binary Search),是一种在有序数组中查找某一特定元素的搜索算法。我们可以从定义可知,运用二分搜索的前提是数组必须是有序的,这里需要注意的是,我们的输入不一定是数组,也可以是数组中某一区间的起始位置和终止位置。
下面我们通过一个例子来帮助我们理解。我们需要在 nums 数组中,查询元素 8 的索引:

int[ ]  nums = {1,3,4,5,6,8,12,14,16}; target = 8  

(1)我们需要定义两个指针分别指向数组的头部及尾部,这是我们在整个数组中查询的情况,当我们在数组某一区间进行查询时,可以输入数组,起始位置,终止位置进行查询。
在这里插入图片描述

(2)找出mid,该索引为 mid =(left + right)/ 2,但是这样写有可能溢出,所以我们需要改进一下写成mid = left +(right - left)/ 2 或者 left + ((right - left ) >> 1) 两者作用是一样的,都是为了找到两指针的中间索引,使用位运算的速度更快。那么此时的 mid = 0 + (8-0) / 2 = 4。
在这里插入图片描述
(3)此时我们的 mid = 4,nums[mid] = 6 < target,那么我们需要移动我们的 left 指针,让left = mid + 1,下次则可以在新的 left 和 right 区间内搜索目标值,下图为移动前和移动后:
在这里插入图片描述
(4)我们需要在 left 和 right 之间计算 mid 值,mid = 5 + (8 - 5)/ 2 = 6 然后将 nums[mid] 与 target 继续比较,进而决定下次移动left 指针还是 right 指针,见下图:
在这里插入图片描述
(5)我们发现 nums[mid] > target,则需要移动我们的 right 指针, 则 right = mid - 1;则移动过后我们的 left 和 right 会重合,这里是我们的一个重点大家需要注意一下,后面会对此做详细叙述。
在这里插入图片描述
(6)我们需要在 left 和 right 之间继续计算 mid 值,则 mid = 5 +(5 - 5)/ 2 = 5 ,见下图,此时我们将 nums[mid] 和 target 比较,则发现两值相等,返回 mid 即可 ,如果不相等则跳出循环,返回 -1。
在这里插入图片描述
二分查找的执行过程如下:
1.从已经排好序的数组或区间中,取出中间位置的元素,将其与我们的目标值进行比较,判断是否相等,如果相等则返回。
2.如果 nums[mid] 和 target 不相等,则对 nums[mid] 和 target 值进行比较大小,通过比较结果决定是从 mid的左半部分还是右半部分继续搜索。如果 target > nums[mid] 则右半区间继续进行搜索,即 left = mid + 1; 若target < nums[mid] 则在左半区间继续进行搜索,即 right = mid -1;

动图解析
在这里插入图片描述
下面我们来看一下二分查找的代码,可以认真思考一下 if 语句的条件,每个都没有简写。

 public static int binarySearch(int[] nums,int target,int left, int right) {
        //这里需要注意,循环条件
        while (left <= right) {
            //这里需要注意,计算mid
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == target) {
                return mid;
            }else if (nums[mid] < target) {
                //这里需要注意,移动左指针
                left  = mid + 1;
            }else if (nums[mid] > target) {
                //这里需要注意,移动右指针
                right = mid - 1;
            }
        }
        //没有找到该元素,返回 -1
        return -1;
    }

二分查找的思路及代码已经理解了,那么我们来看一下实现时容易出错的地方:
1.计算 mid 时 ,不能使用 (left + right )/ 2,否则有可能会导致溢出
2.while (left < = right) { } 注意括号内为 left <= right ,而不是 left < right ,我们继续回顾刚才的例子,如果我们设置条件为 left < right 则当我们执行到最后一步时,则我们的 left 和 right 重叠时,则会跳出循环,返回 -1,区间内不存在该元素,但是不是这样的,我们的 left 和 right 此时指向的就是我们的目标元素 ,但是此时 left = right 跳出循环。
3.left = mid + 1,right = mid - 1 而不是 left = mid 和 right = mid。我们思考一下这种情况,见下图,当我们的target 元素为 16 时,然后我们此时 left = 7 ,right = 8,mid = left + (right - left) = 7 + (8-7) = 7,那如果设置 left = mid 的话,则会进入死循环,mid 值一直为7 。
在这里插入图片描述
下面我们来看一下二分查找的递归写法:

public static int binarySearch(int[] nums,int target,int left, int right) {
        
        if (left <= right) {
            int mid = left + ((right - left) >> 1);
            if (nums[mid] == target) {
                //查找成功
                return  mid;
            }else if (nums[mid] > target) {
                //新的区间,左半区间
                return binarySearch(nums,target,left,mid-1);
            }else if (nums[mid] < target) {
                //新的区间,右半区间
                return binarySearch(nums,target,mid+1,right);
            }
        }
        //不存在返回-1
        return -1;
    }

leetcode35搜索插入位置

解析过程: leetcode35搜索插入位置

leetcode34在排序数组中查找元素的第一个和最后一个位置

解析过程:leetcode34在排序数组中查找元素的第一个和最后一个位置

2 不完全有序

查找目标元素(不含重复元素)

之前我们说二分查找需要在完全有序的数组里使用,那么不完全有序时可以用吗?
例:在这里插入图片描述
上面的新数组虽然不是完全有序,但是也可以看成是由一个完全有序的数组翻折得到的。或者可以理解成两个有序数组,且第二个数组的最大值小于第一的最小值,我们将其拼接,拼接成了一个不完全有序的数组,在这个数组中我们需要找到 target ,找到后返回其索引,如果没有找到则返回 -1;我们第一次看到这种题目时,可能会想到,我们只需要挨个遍历就好啦,发现后返回索引即可,这样做当然是可以滴,那么我们可不可以使用二分查找呢?

下面我们看一下解决该题的具体思路。
首先我们设想一下 mid 值会落到哪里,我们一起来想一下。是不是只有两种情况,和 left 在一个数组,同时落在 数组1 或同时在 数组2,或者不在一个数组, left 在数组1,mid 在数组2。想到这里咱们这个题目已经完成一半了。
在这里插入图片描述
那么我们先来思考一下,?我们可以根据 nums[mid] 和 nums[left] 判断,是因为我们的 mid 一定是会落在 left 和 right 之间,那如果 nums[mid] >= nums[left] 时,说明他俩落在一个数组里了,如果 nums[mid] < nums[left] 时,说明他俩落在了不同的数组,此时left 在数组1 mid在数组2.

注:left 和 mid 落在同一数组时,不能是 left 在 数组2 ,mid 在 数组1 呢?因为咱们的 mid 是通过 left 和 right 的下标求得,所以应该在 left 和 right 中间
如果我们的 mid 和 left 在同一个数组内时?咱们的 target 会有几种情况呢?我们通过都落在 数组1 举例。
在这里插入图片描述
无非也是两种情况,用我们上面的例子来说,

1.落在 mid 的左边,当前例子中 情况是落在 [4,7)区间内,即 4 <= target < 7 ,也就是 target >= nums[left] && target < nums[mid],此时我们让 right = mid -1,让 left 和 right 都落到数组 1 中,下次查找我们就是在数组1中进行了,完全有序,

2.落在 mid 的右边,此时例子中 target 不落在 [4,7)区间内,那就 target = 8 或 0 <= target <= 2 (此时我们的 target 均小于 nums[left]) 两种情况,也就是target > nums[mid] || target < nums[left] 此时我们让 left = mid + 1即可,也是为了慢慢将left 和 right 指针赶到一个有序数组内。

那我们在来思考一下当 mid 值落在 数组2 中时,target 会有几种情况呢?其实和上面的例子思路一致,情况相反而已。在这里插入图片描述

  1. target <= nums[right] && target > nums[mid]
    这里和上面的对应,此时的情况就是整个落在右半部分,我们下次就可以在数组2内进行查找。
  2. target > nums[right] || target < nums[mid]
    这里就是和上面的第二种情况对应,落在 mid 的左半部分,我们尽量将两个指针赶到一起.

leetcode33搜索旋转排序数组

题目:给你一个整数数组 nums ,和一个整数 target 。

该整数数组原本是按升序排列,但输入时在预先未知的某个点上进行了旋转。(例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

示例 3:

输入:nums = [1], target = 0
输出:-1

解析
这个题目的解答方法,咱们在上面已经有所描述,下面我们来看一下下面这个例子的代码执行过程吧.

输入 nums = [4,5,6,7,8,0,1,2] target = 8

代码:

class Solution {
    public int search(int[] nums, int target) {
        //左右指针
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left+((right-left)>>1);
            if (nums[mid] == target) {
                return mid;
            }
            //落在同一数组的情况,同时落在数组1 或 数组2
            if (nums[mid] >= nums[left]) {
                //target 落在 left 和 mid 之间,则移动我们的right,完全有序的一个区间内查找
                if (nums[mid] > target && target >= nums[left]) {
                       right = mid - 1;
                // target 落在right和 mid 之间,有可能在数组1, 也有可能在数组2
                } else if (target > nums[mid] || target < nums[left]) {
                       left = mid + 1;
                } 
            //不落在同一数组的情况,left 在数组1, mid 落在 数组2
            }else if (nums[mid] < nums[left]) {
                //有序的一段区间,target 在 mid 和 right 之间
                if (nums[mid] < target && target <= nums[right]) {
                    left = mid + 1;
                // 两种情况,target 在left 和 mid 之间
                } else if (target < nums[mid] || target > nums[right]) {
                    right = mid - 1;
                }
            } 
        }
        //没有查找到
        return -1;
    }
}

查找目标元素(含重复元素)

我们通过刚才的例子了解了,如果在不完全有序的数组中查找目标元素,但是我们的不完全有序数组中是不包含重复元素的,那如果我们的数组中包含重复元素我们应该怎么做呢?见下图:
在这里插入图片描述
如上图,如果我们继续使用刚才的代码,则会报错这是为什么呢?我们来分析一下。在这里插入图片描述
所以我们需要对其进行改进,我们只需将重复元素去掉即可,当我们的 nums[left] == nums[mid] 时,让 left ++ 即可,比如 1,3,1,1,1此时 nums[mid] == nums[left] 则 left ++,那我们此时会不会错过目标值呢?其实并不会,只是去掉了某些重复元素,如果此时我们的目标元素是3,则我们left++,之后情况就变为了上题中的情况。

leetcode81. 搜索旋转排序数组 II

题目
假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。

编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

示例 1:

输入:nums = [2,5,6,0,0,1,2], target = 0 输出:true

示例 2:

输入:nums = [2,5,6,0,0,1,2], target = 3 输出:false

解析
这个题目就比刚才的不含重复元素的题目多了一个去除某些重复元素的情况,当 nums[mid] == nums[left] 时,让 left++,并退出本次循环,其余部分完全相同,大家可以结合代码和图片进行理解。

代码

在这里插入图片描述

寻找最小值

这种情况也很容易处理,和咱们的leetcode33搜索旋转排序数组,题目类似,只不过一个需要搜索目标元素,一个搜索最小值,我们搜索目标元素很容易处理,但是我们搜索最小值应该怎么整呢?见下图在这里插入图片描述
我们需要在一个旋转数组中,查找其中的最小值,如果我们数组是完全有序的很容易,我们只需要返回第一个元素即可,但是此时我们是旋转过的数组。我们需要考虑以下情况:
在这里插入图片描述
我们见上图,我们需要考虑的情况是

  • 数组完全有序 nums[left] < nums[right],此时返回 nums[left] 即可
  • left 和 mid 在一个都在前半部分,单调递增区间内,所以需要移动 left,继续查找,left = mid + 1;
  • left 在前半部分,mid在后半部分,则最小值必在 left 和 mid 之间(见下图)。则需要移动right ,right = mid,我们见上图,如果我们 right = mid - 1,则会漏掉我们的最小值,因为此时 mid 指向的可能就是我们的最小值。所以应该是 right = mid 。

在这里插入图片描述
leetcode 153 寻找旋转数组中的最小值
题目
假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 。

请找出其中最小的元素。

示例 1:

输入:nums = [3,4,5,1,2]输出:1

示例 2:

输入:nums = [4,5,6,7,0,1,2] 输出:0

示例 3:

输入:nums = [1] 输出:1

解析
我们在上面的描述中已经和大家分析过几种情况,下面我们一起来看一下,[5,6,7,0,1,2,3]的执行过程,相信通过这个例子,大家就能把这个题目整透了。
在这里插入图片描述
代码
在这里插入图片描述

3 二维数组

下面我们来看一下另外一种变体,如何在二维矩阵里使用二分查找呢?

其实这个很简单,只要学会了二分查找,这个完全可以解决,我们先来看一个例子。

我们需要从一个二维矩阵中,搜索是否含有元素 7,我们如何使用二分查找呢?其实我们可以完全将二维矩阵想象成一个有序的一维数组,然后用二分,,比如我们的二维矩阵中,共有 9 个元素,那定义我们的 left = 0,right = 9 - 1= 8,是不是和一维数组定义相同,然后我们求我们的 mid 值, mid = left +((right - left) >> 1)此时 mid = 4 ,但是我们的二维矩阵下标最大是,nums[2,2]呀,你这求了一个 4 ,让我们怎么整呀。如果我们理解了二分查找,那么这个题目考察我们的应该是如何将一维数组的下标,变为 二维坐标。其实也很简单,咱们看哈,此时咱们的 mid = 4,咱们的二维矩阵共有 3行, 3列,那我们 mid =4,肯定在第二行,那么这个应该怎么求得呢?

我们可以直接用 (mid/列数),即可,因为我们 mid = 4,4 /3 = 1,说明在 在第二行,那如果 mid = 7 ,7/3=2,在第三行,我们第几行知道了,那么我们如何知道第几列呢?我们可以直接根据 (mid % 列数 )来求得呀,比如我们此时 mid = 7,7%3 = 1,那么在我们一维数组索引为 7 的元素,其处于二维数组的第2列,大家看看下图是不是呀!

在这里插入图片描述
下面我们来看一下 leetcode 74题,让我们给他整个通透。

搜索二维矩阵

题目
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。

示例1

输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 3
输出:true

示例2

输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 13
输出:false

示例3

输入:matrix = [], target = 0
输出:false

解析
在上面我们已经解释了如何在二维矩阵中进行搜索,这里我们再对其进行一个总结,就是我们凭空想象一个一维数组,这个数组是有二维数组一层一层拼接来的,也是完全有序,然后我们定义两个指针一个指向一维数组头部,一个指向尾部,我们求得 mid 值然后将 mid 变成二维坐标,然后和 target 进行比较,如果大于则移动 left ,如果小于则移动 right 。

代码:

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
           
           if (matrix.length == 0) {
               return false;
           }
           //行数
           int row = matrix.length;
           //列数
           int col = matrix[0].length;
           int left = 0;
           //行数乘列数 - 1,右指针
           int right = row * col - 1;
           while (left <= right) {
               int mid = left+ ((right-left) >> 1);
               //将一维坐标变为二维坐标
               int rownum = mid / col;
               int colnum = mid % col;
               if (matrix[rownum][colnum] == target) {
                    return true;
               } else if (matrix[rownum][colnum] > target) {
                   right = mid - 1;
               } else if (matrix[rownum][colnum] < target) {
                   left = mid + 1;
               }
           }
           return false;
    }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LVS(Linux Virtual Server)是一种基于 Linux 系统的负载均衡集群技术,它主要用于将网络流量分发到多个服务器上,以提高系统的可靠性、可扩展性和性能。 LVS 集群一般包括四个组件:调度器(LVS 调度器)、前端服务器(负载均衡器)、后端服务器(真实服务器)和存储服务器(用于共享数据)。首先,调度器接收来自客户端的请求,然后根据配置的调度算法(如轮询、加权轮询、最小连接数等)将请求分发到多个前端服务器。前端服务器接收到请求后,通过相应的负载均衡算法将请求转发到后端的真实服务器上进行处理。在整个过程中,存储服务器用于存放共享的数据,以确保所有的真实服务器都能获取到相同的数据,并提供一致的服务。 LVS 集群的优点是能够提高网站的稳定性和可靠性,当某一台服务器出现故障时,调度器会自动将请求分发到其他可用的服务器上,从而保证服务的连续性。同时,LVS 集群还能够通过增加前端服务器和后端服务器的数量来提高系统的性能和吞吐量,以满足不断增长的用户需求。 在实际应用中,LVS 集群需要合理配置,包括选择合适的调度算法、调整每台服务器的权重、选择适当的硬件设备等。此外,还需要及时监控集群的运行状态,及时发现和解决故障,以确保整个系统的正常运行。 总的来说,LVS 负载均衡集群是一种强大而高效的集群技术,能够帮助企业提高系统的可靠性和性能,是现代互联网应用中不可或缺的重要组成部分。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值