【LeetCode刷题笔记】二分查找

news2024/11/20 7:06:52

1539. 第 k 个缺失的正整数

解题思路:
  • 1. 二分 ,一个严格升序 正整数 数组在没有数字缺失的情况下满足: nums[i] = i + 1 ,如果有缺失,则每个 下标 i  上的数字前面缺失的正整数个数为: nums[i] - (i + 1) (没有缺失的情况 num[i] - (i + 1) 正好等于 0)
  • 因此可以在 下标 [0, N] 上二分 ,查找目标 前面缺失的正整数个数 ≥ k 的第一个数
  • 每次二分判断的点就是【 mid前面缺失的正整数个数 】: miss = nums[mid] - (mid + 1)
  • 如果 miss < k 就往 右边二分 ,如果 miss >= k , 就往 左边二分
  • 跳出二分循环时, L == R R 就是要找的下标,即该位置上的数是第一个满足 前面缺失的正整数个数 ≥ k 条件
  • 最终缺失的数字就是 R + k 。 

 

我们总结一下:

  • 1)二分查找的数组不是原始数组,而是由原始数组的每个元素通过公式 nums[i] - (i + 1) 生成的新的数组上进行二分
  • 2)二分查找的目标是大于等于 k 的第一个位置下标

当我们找到了这样满足条件的下标之后(也就是退出 while(L < R) 循环时的 R),该如何计算缺失的第个数呢?这时我们可以将包括 R 位置的数在内和前面缺失的数字组成的数组区间进行一下重新分布,如下图:

也就是说找到了 R 即找到了缺失的数字原本的下标是 R + (k - 1) ,我们回顾一下数组中没有缺失数字的情况:

所以我们直接用 R + k 就算出了第 k 个缺失的数字。

此外,我们还有两种特判情况,可以简化处理:

注意:这里 R 要取到数组的长度 N,而不是 N - 1,这是因为有可能数组中所有数字前面缺失的个数都小于 k,也就是说第 k 个缺失数字排在数组之后。这样 L 会不断往右边缩,最终退出循环时,L == R == N,这样通过 R + k 计算不会错过答案。参考下面的例子理解:

假设 R 初始取 N - 1,这里退出循环时,R = 4,R + k 得到的第 9 个数会是 13,是错误答案。

由于二分查找的过程完全覆盖了前面提到的两种特判情况,因此也可以完全省略掉特判代码,直接像下面这样写:

只不过加上特判对某些测试用例可以更快速的通过。

注意,这个题能用二分查找的前提是题目数组是升序排的,因此通过公式 nums[i] - (i + 1) 生成的对应的新数组也是升序排的。

解题思路: 
  • 2. 线性查找 ,查找判断的条件跟方法1一样,只不过由二分变成 顺序 查找 前面缺失的正整数个数 ≥ k 的第一个数

相对而言,这种方法的代码更加简单,但时间复杂度是 O(n),没有二分的 O(logn) 高效。这里 R 仍然要取到 N,因为数组中可能找不到 ≥ k 的第一个下标,方法1中已经分析过了。

278. 第一个错误的版本

解题思路: 
  • 二分查找 ,因为版本号是一个从1开始的升序序列,所以题目等价于二分查找第一个等于目标元素的值,即在一个升序数组中查找第一个符合错误版本的数字。
二分查找-区间排除法:

 

二分查找-前瞄法: 

 

前瞄法的另一种等价的写法:

剑指 Offer 53 - II. 0~n-1中缺失的数字

解题思路: 
  • 1. 二分查找 如果数组中 没有缺失  0~n-1 任何数,则满足 nums[i] = i   如果缺了某个数, 从缺的那个数开始就不满足 nums[i] = i 这个关系。
  • 因此数组被分成两部分: 左半部分 nums[i] = i 右半部分 nums[i] != i
  • 分查找第一个满足 nums[i] != i  这种关系的 i 就是答案。
  • 注意: 如果数组中所有元素都满足 nums[i] == i ,则缺失的数是数组长度 N

 

二分查找第一个满足某种关系的下标,有两种方法:前瞄法和区间排除法

解题思路:   

  • 2. 线性查找,遍历找到第一个满足 num[i] != i 的 i 就是答案。

2035. 将数组分成两个数组并最小化数组和的差

解题思路: 
  • 分治 + 二分 / 有序表 首先将数组分成等长的两半,对每一半的数组,通过 DFS 求出其中选 x 个数的 sum 和是多少(用 Map<Int, TreeSet> 存储因为选 x 个数的和可能有多个)
  • 然后从两半数组生成的 2个Map 中寻找 N / 2 个数的组合,使其和最接近 allSum / 2 ,记作 pickSum ,而剩余的 N / 2 个数组成的数组的和就是: restSum = allSum - pickSum ,然后每次求 abs(pickSum - restSum) 并记录 最小值 就是答案。
  • Map 也可以用列表存选 x 个数的和,完了用【二分查找最后一个小于等于目标的元素】替代 TreeSet.floor

首先看一下如何通过 DFS 求一个数组中选 x 个数的 sum 和是多少:

其中 TreeSet.floor() 返回 ≤ target 的最大值,也就是 ≤ target 的最后一个元素。

因此,我们也可以使用二分查找来代替 TreeSet.floor() 的功能,参考代码如下:

852. 山脉数组的峰顶索引

解题思路: 
  • 1. 线性查找 双指针 O(N) 参考941

解题思路: 
  • 2. 二分查找 区间排除法
  • 若  A[mid] < A[mid + 1] 说明  mid  处在 上坡 峰值 肯定在  [mid+1, R]  区间
  • 若  A[mid] > A[mid + 1] 说明  mid  处在 下坡 峰值 肯定在  [L, mid]  区间

由于题目给的是山脉数组,山峰一定存在,所以二分查找一定能找到结果,所以最后返回时可以不需要判断L==R且山峰在两端。

问题思考:常规的二分查找是在一个单调有序数组上查找,但是本题并不是一个有序数组,为什么也能用二分呢?

  • 实际上题目中的数组包含一个单调递增序列(峰值左边)和一个单调递减序列(峰值右边),我们只是不知道两个序列的分割点,即峰值在哪里。 
  • 对于一个范围 [i, j],我们可以先找到范围 [i, j] 中间连续的两个点 mid 与 mid+1。如果 arr[mid] < arr[mid+1] ,那么可以知道峰值在范围 [mid + 1, j] 内;如 果 arr[mid] > arr[mid+1] ,那么可以知道峰值在范围 [i, mid] 内。通过这样的方法,我们可以在 O(logn) 的时间内找到峰值所处的下标。

这本质上是因为峰值一定是处在单调递增序列的右边和单调递减序列的左边,也就是说对于任意一段区间而言峰值非左即右,峰值在方向上具有某种单调性,所以才能使用二分利用这种单调性来求解。

注意,941题是给一个数组判断其是否是山脉数组,这个数组不一定是山脉数组,而本题是给出一个山脉数组,找出其山峰,所以941是不能用二分查找来判断的,但是本题可以。 

1095. 山脉数组中查找目标值

解题思路: 
  • 可以通过 三次二分法查找 来完成:
  • 1)同852,先使用 二分法 找到山脉数组的 峰顶索引
  • 2)在 上坡升序数组中二分查找目标值 ,找到就返回
  • 3)第2步没找到,再到 下坡降序数组中二分查找目标值

实际上就是通过找到山峰来将数组分成了两个有序数组,在这两个有序数组中查找目标值,由于题目是要找第一个等于目标的下标值,所以需要先找上坡的,再找下坡的。

由于山脉数组是由严格单调递增/递减的序列组成,不含重复元素,所以使用标准的二分查找代码即可(不需要使用前瞄法)。

162. 寻找峰值

解题思路: 
  • 代码同852,只要找到一个山峰即可,但是本题能用 二分 有前提条件:
  • 1) nums[-1] = nums[n] = -∞  
  • 2)由于第一条,整个数组中一定存在至少一个峰值
  • 3)无重复值,数据有排他性

 

问题思考:为什么我们这样做二分到最后一定不会错过峰值?

当 mid 落到任意一个位置时,只可能是以下四种情况之一:

  • ① 处于某个山峰,当前就是峰值
  • ② 处在某个上坡,右边的值比当前大,右边绝对存在一个峰值
  • ③ 处在某个下坡,左边的值比当前大,左边绝对存在一个峰值
  • ④ 处在某个山谷,往左往右都能找到山峰

35. 搜索插入位置

解题思路:
  • 二分查找第一个大于等于 target 的元素下标
  • 注意:如果没有找到  ≥  target  的位置,即数组元素全部 < target,则 target 应插入到数组的末尾,也就是数组长度的位置。

 二分查找第一个大于等于target的元素下标有两种方法:前瞄法 和 区间排除法

 前瞄法实现:

 

区间排除法实现: 

 

 区间排除法的另一种写法:

以上两种区间排除法的代码中,如果把 R 初始化为 N,或者把 res 初始化为 N,就可以省略掉前面的 target 比最后一个元素大的特判条件,因为这时代码包括了这一种情况。

74. 搜索二维矩阵

解题思路: 
  • 二维数组模拟想象成一维有序数组进行二分查找 ,二分范围 [0,M * N - 1] ,对每次二分的一维下标点 mid 转换成二维坐标值 matrix[ mid / N ][ mid % N ]

由于二维数组的有序特性,可以将二维数组看成一个一维有序数组,然后对其进行二分查找,但并不需要真正的将二维数组转成一维数组,只需通过坐标关系转换就可以计算得到原始位置的元素值。

这个题主要需要记住一维坐标 index 和二维坐标 [i, j] 的转换关系:i = index / N,j = index % N,其中 N 列数,因为按照一行一行的摆放的话,决定什么时候换行的是列数。

240. 搜索二维矩阵 II

解题思路:
  • 1. 二分查找 对角线上的每个元素为起点 ,对 每一行 每一列 应用 二分查找
  • 对角线的长度: Math.min(M, N) ,从 i = 0 开始进行这些轮二分就可以,每次是对 第 i 行、第 i 列起点 (i, i) 二分

解题思路: 

  • 2. 线性查找,从左下角出发,遇到比目标小的往右走,遇到比目标大的往上走,遇到相等的直接返回。(或者也可以从右上角出发也具有单调性)

 遇到比目标小的往右走,是因为右边的元素更大,目标肯定在当前元素的右边。遇到比目标大的往上走,是因为上边的元素更小,目标肯定在当前元素的上边。

33. 搜索旋转排序数组

解题思路:
  • 1. 二分 ,先判断 [L, mid] [mid + 1, R]  哪段是 有序 的,然后判断 target 是否落在这段有序区间内,如果是就收缩对应边界至当前有序的这段区间上 ,否则就去另一边。
  • 每次二分时如何判断哪一部分是有序的:若 nums[L] ≤ nums[mid] ,则  [L, mid]  是有序的,否则  [mid + 1, R]  是 有序的。

这启示我们可以在常规二分查找的时候查看当前 mid 为分割位置分割出来的两个部分 [L, mid] 和 [mid +1, R] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分查找的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分:

  • 如果 [L, mid] 是有序数组,且 target ∈ [nums[L] , nums[mid]),则我们应该将搜索范围缩小至 [L, mid - 1],否则在 [mid +1, R] 中寻找。
  • 如果 [mid + 1, R] 是有序数组,且 target ∈ (nums[mid],nums[R]],则我们应该将搜索范围缩小至 [mid +1, R],否则在 [L, mid - 1] 中寻找。

解题思路:
  • 2.  利用极值转成有序数组 防御编程思想 ,若 target < nums[0] target 右半边 ,若 target > nums[0] target 左半边
  • target 右半边 时,如果 mid 左半边 ,将 mid 的值改成 -infinity ,当 target 左半边 时,如果 mid 右半边 ,将 mid 的值改成 +infinity
  • 在二分查找的过程中,不断执行上面操作, 整个数组最终变成一个有序数组。

例如,对于旋转数组 nums = [4,5,6,7,0,1,2],首先根据 nums[0] 与 target 的关系判断 target 是在左段还是右段。

  • 例如 target = 5,目标值在左半段,因此在 [4, 5, 6, 7, inf, inf, inf] 这个有序数组里找就行了;
  • 例如 target = 1,目标值在右半段,因此在 [-inf, -inf, -inf, -inf, 0, 1, 2] 这个有序数组里找就行了。
  • 如此,我们又双叒叕将「旋转数组中找目标值」 转化成了「有序数组中找目标值」

 注意:这里只需要与 nums[0] 进行比较就可以定位 target 或 nums[mid] 是位于左半边还是右半边了(不能与nums[L]b比较)。

81. 搜索旋转排序数组 II

解题思路:
  • 二分 + 去重 ,同33,只是多了 重复元素 当数组中有重复数时,可能会有 arr[L] = arr[mid] = arr[R] ,此时 无法判断 [L, mid] 和 [mid+1, R] 哪个是有序 的, 此时我们只需要让  L++ R-- ,跳过相同的值,然后在新区间上继续二分查找。

例如 nums=[3,1,2,3, 3,3,3],target=2,首次二分时无法判断 [0,3] (即[3,1,2,3]) 和 [4,6](即[3,3,3]) 哪个是有序的,因为33题的二分中我们判断哪一段有序是根据 num[L] 与 nums[mid] 的大小来判断的,但此时二者都是相等的,所以无法做出判断。对于这种情况,我们只能选择 L++R--,然后在新区间上继续二分查找。

问题思考: L++R-- 这个操作在跳过相同的重复元素时,有没有可能会跳过答案呢?

  • 在上面代码中,nums[L] == nums[mid] && nums[mid] == nums[R] 的 if 判断是写在 nums[mid] == target 的 if 判断后面的,也就是说执行到这里时,nums[mid] 肯定不等于 target,因此收缩两边相同的值,当然也不会跳过答案了。(这也提醒我们要注意这两个if判断的先后顺序,如果颠倒了肯定不行)

153. 寻找旋转排序数组中的最小值

解题思路:
  • 1. 二分 - 区间排除法 ,按照 中值 右值 的大小关系, 判断最小值在中值的哪一侧 ,该收缩哪一侧。
  • 中值 < 右值 ,最小值在中值 左边 ,收缩 R = mid
  • 中值 > 右值 ,最小值在中值 右边 ,收缩 L = mid + 1

 

解题思路:

  • 2. 类似方法1,按照中值左值的关系判断最小值在哪一侧,当在二分过程中出现 nums[L] <= nums[R] 时,nums[L] 就是最小值

154. 寻找旋转排序数组中的最小值 II

解题思路:
  • 二分 + 去重,同153,不同的是本题数组有重复元素,当有重复元素时,可能出现 中值等于右值 的情况,此时无法判断最小值和中值的位置关系,对于这种情况,我们可以选择 舍弃一个右值 ,因为不管舍弃的右值是不是最小值,它都还有一个 中值做替补 ,因此 舍弃右值最终不会漏掉最小值

4. 寻找两个正序数组的中位数

解题思路:
  • 1.  合并两个有序数组 ,若合并后数组长度为偶数,则中位数取 (arr[N / 2 - 1] + arr[N / 2]) / 2 ,数组长度为奇数,则中位数取 arr[N / 2] ,但时间复杂度是 O(m+n) ,不合题目要求。
  • 2.  划分数组 + 二分查找 ,O(log(m+n)),在任意位置划一刀,在分割线左右求 maxLeft minRight

我们在两个数组任意位置划一刀,将数组 A左半部分和数组 B左半部分记作 leftPart,同理右半部分记作 rightPart

 综上,我们的算法就是:

  • [0, m] 上执行二分,每次二分中,让  i = (L + R) / 2 (即mid值) ,j = (m + n + 1) / 2 - i 如果  A[i - 1] <= B[j] 就让  L = i + 1  到右边去找符合该条件的更大的 A[i - 1] 同时更新 maxLeft =  max(A[i - 1], B[j - 1]) minRight =  min(A[i], B[j]) 否则就让  R = i - 1
  • 注意点:在取 A B 值时,如果出现越界,分别取 -inf inf
  • 最终答案返回 (maxLeft + minRight) / 2 ( m + n 是偶数时 ) 或 maxLeft ( m + n 是奇数时 )

378. 有序矩阵中第 K 小的元素

解题思路:
  • 1. 二分 ,在矩阵中 [最小值,最大值] 这个区间上二分查找,每次二分后统计矩阵中 ≤ mid 的元素数量 ,然后根据这个 数量与 k 的大小关系 决定继续往哪边二分。
  • 因为题目求的是所有元素排序后从小到大数到第 k 个的元素是谁,是与 数量 相关的,所以在二分过程中判断比较的条件就是 ≤ mid 的元素数量
  • 如果这个 数量 > k 就去 左边 二分,如果这个 数量 < k 就去 右边 二分。
  • 在统计 ≤ mid 的 数量 时,从矩阵 左下角 出发,遇到大的 往上走 ,遇到小的 往右走  并同时 计数 。 (或者从右上角出发也可以)

在统计 ≤ mid 的数量时,从矩阵的左下角开始向右向上统计,这个点的两个方向都具有单调性。具体的,每当移动到一个 [i, j] 位置时,如果 matrix[i][j] <= mid,就累加这一列的区间 [0, i] 的元素个数 i + 1 即可(该列中的 i 之上的数都 ≤ mid)。

这个代码其实是查找的 count 大于等于 k 的第一个位置。当然,也可以使用前瞄法来实现这个代码:

问题思考:我们代码二分查找的是 [min, max] 区间而非矩阵下标,那有没有可能最后找到的数是不在matrix矩阵中的数?即如何证明找到的数一定是矩阵中的数?

  • 我们可以使用反证法,假设我们最后找到了一个数 X ∈ [min, max],它是满足【矩阵中小于等于 X 的个数】≥ k 的 [min, max] 区间中的第一个数, 但是它不在矩阵中,那就是说矩阵中排在它前面的个数其实是最多 k - 1 个(减去一个 X),而矩阵中肯定存在第 k 小的数 Y,Y 肯定大于 X(否则 X 不会是第一个满足条件的,而是第二个了) ,此时就发生矛盾了,矩阵中前 k - 1 个数以及 Y 总共 k 个数都满足小于等于 Y,Y 应该是【矩阵中小于等于 Y 的个数】≥ k 的第一个数,且 Y ∈ [min, max],但是前面说 X 是 [min, max] 区间中第一个满足这个条件的,那么 Y 和 X 应该是同一个数啊,这与前面得出 Y 肯定大于 X 的结论冲突,所以 X 如果不在矩阵中,这里就自相矛盾了。因此 X 必在矩阵中。这里有点绕,如果不理解,可以套一个具体的例子来分析。
解题思路:
  • 2. 小根堆 + 归并排序 ,矩阵的每一行均为一个有序数组,问题即转化为从这 n 个有序数组 中找 k  小 的数,可以使用 归并排序 的做法,一般归并排序是 两个数组 归并,而本题是 n 个数组归并,所以要用 小根堆 维护。
  • 先把 每行开头的第一个数 ,也就是 第一列 每个值 坐标 放入 小根堆 然后循环取出 堆顶, 并将 堆顶元素的所在行的下一个相邻元素放入小根堆 这样循环取  k 次 后,则当前取到的堆顶就是第  小的元素。

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

解题思路:
  • 两次二分 ,分别查找 第一个等于目标的下标 最后一个等于目标的下标

前瞄法/后瞄法: 

区间排除法: 

  

注意:查找最后一个等于target的下标时,计算mid的地方要+1,否则可能出现死循环。

可见,查找第一个/最后一个等于目标的元素时,使用前瞄法/后瞄法在逻辑上比较好理解,区间排除法虽然代码简洁一点,但是需要小心留意的地方比较多(比如while循环条件没有等号、大于等于或小于等于target的条件中边界指针只能收缩到mid、mid值的计算有时需要+1处理) 。

剑指 Offer 53 - I. 在排序数组中查找数字 I

解题思路:
  • 两次二分查找,同34题,当找到第一次和最后一次出现的索引下标之后,计算这两个下标之间的区间长度即可。

 

875. 爱吃香蕉的珂珂

解题思路:
  • 对速度值值的范围区间进行二分,在 [1, max]  这个速度范围上进行二分,其中 max  代表数量最多的那一堆香蕉。
  • 珂珂可以选择以 1 根/小时 的速度吃,也可以选择以最大速度 max 根/小时 的速度吃。求解能在 h小时 内吃完所有香蕉的 最小速度值
  • 每次二分来到一个中值速度 mid ,计算一下以当前 mid  根/小时 的速度吃完所有香蕉花费的时间,是否可以 不超过 h 小时 如果可以,则到 左边 继续二分寻找 最小值 ,否则到 右边 二分。

注意:这个题的输入数组有迷惑性,可以把所有香蕉看成是一个整体,然后以不同的速度去吃,我们能够自己推算出最小和最大速度值,得到一个速度值的范围区间,这个区间才是需要去做二分查找的区间。也不要被 h 小时迷惑,它只不过是计算二分判断条件的一个影响比较因子而已。

上图的 speed 数组就是需要真正执行二分查找的数组,而非题目输入的 piles 数组。

我们需要在这个 speed 数组中查找满足某个条件(在h小时内吃完)的最小的/第一个元素值。可以通过区间排除法来实现。

 

面试题 - 截木头

解题思路:
  • 对于二分的变形题,关键点是要一眼看出题目 所求的值的类型是什么 比如本题是求长度 m 的最大值,那么就需要知道 长度 m 的区间范围 如果题目没有指明或限制,可自己推测,如本题 m 的区间范围最小肯定是 1 ,即长度为 1 的木块,最大值应该是数组中的 最长的 那一块木头(可通过一次遍历求得最大值 max ),因此我们就确定了二分的区间是 [1, max]
  • 接下来就是确定每次来到中值点 mid 后,需要进行的比较判断条件是什么,本题是需要计算数组中所有木头截出长度 mid 的块数 count (同样可通过遍历一次数组求得)
  • 求得了 mid 能得到的 块数 count ,下一步就是按照常规的二分思想去比较判断并调整边界了,
  • 1)如果  块数 count < k,说明截取的木头数量少了,要截取更多数量的木头,那就要选择更短的长度,所以到左边区间二分,R = mid - 1
  • 2)如果  块数 count ≥ k 说明截取的木头数量足够k个,但是题目要求最大的 mid,所以到右边区间二分,L = mid + 1

 

上图的 m 数组就是真正需要进行二分查找的隐藏数组,而非题目输入的 nums。这里可以把 count 看成是 m 的附带属性就好理解了(即每一个 m 值都能通过计算生成一个配对的 count 值)。

我们需要在这个 m 数组中查找满足某个条件(count ≥ k)的最后一个元素值。有两种二分的方法:区间排除法 和 后瞄法。

二分-区间排除法: 

注意:如果是采用区间排除法,计算mid 时,需要写成 mid = L + (R - L + 1) / 2 的形式,否则可能会死循环,另外在块数count ≥ k时,L 要包含 mid (即带等号的条件里要包含mid否则可能错过答案)。 

本题也可以采用后瞄法的写法,求最后一个截出数量等于 k 的 mid 值,这种写法就不存在排除法的别捏写法,但是前瞄/后瞄的限制是,要查看的下一个值mid + 1是存在于二分区间内的位于 mid 后面的值。 

二分-后瞄法:

这个题跟 875. 爱吃香蕉的珂珂,在思想上其实是十分相似的。 

我们总结一下这两道题,可以发现这种类型的二分查找题目具有以下特点:

  • 1)二分查找的区间是隐藏的,不是直观上题目的输入数组,需要通过观察得到,比如最直接的是看题目求解的最值是什么类型?(最小的速度?最大的长度?)题目求解的是什么类型,就需要想法设法的先找出题目中该类型的区间范围,但是可能有时不是太明确,比如上面两道题中区间的范围都是 [1, max] 这里的 1 和 max 可能需要根据经验常识得到,关于这一点题目都没有给出明确的提示。
  • 2)确定了查找的类型和区间范围之后,后面就好办了,按照常规二分方法进行mid计算,只不过拿到了mid值之后,并不是直接判断,而是往往需要再根据题目给出的限定条件求解一个由mid计算出的某个值,然后根据这个值来决定接下来往哪一边继续二分
  • 3)降低输入数组的存在感,如果输入数组不是用来二分的,那么它唯一的作用就是用来配合每次的 mid 值来计算一个判断条件值,用以划分左右边界。(例如可能是for循环每个元素与mid进行计算得到一个新的值)

这种隐藏查找区间的二分类题目我认为是比较困难的了,因为常规二分题目会直接给出可以进行二分的数组,但是这类隐藏查找区间的就需要自己来找,如果没有意识到这一点,可能会直接拿题目给出的输入数组去做二分的考虑,这样就会陷入一个错误的解题方向的陷阱中。

二分查找代码模板总结

二分查找的基本形式

 

这里区间排除法要尤其注意:while循环条件是 L < R,没有等号,target <= nums[mid]时是取 R = mid,不是 mid - 1。另外在很多题目中,会告诉你查找的目标一定存在(或者我们自己做出判断其一定存在),那么上面代码中最后 return 前面的 if 判断就不需要了,最后L==R往往就是答案。

二分查找的变种形式

第一类:
  • 【前瞄法】 查找第一个等于 target 的下标
  • 【后瞄法】 查找最后一个等于 target 的下标

 

第二类:

  • 【区间排除法】查找第一个等于 target 的下标(或第一个大于等于 target 的)

  • 【区间排除法】查找最后一个等于 target 的下标

 

 

 

第三类:

  • 【前瞄法】查找第一个大于等于 target 的下标

  • 【后瞄法】查找最后一个小于等于 target 的下标

 

 

 

第四类:

  • 【区间排除法】查找最后一个小于等于 target 的下标

 

注:这个也可以归纳到第二类当中,这里单独归为单独一类是想强调这个做法需要在计算mid时尤其小心,必须使用 L + (R - L + 1) / 2 否则会导致死循环

以上几种变种形式中,我们发现【区间排除法】需要注意的点比较多,while 循环条件必须是 L < R,没有等号,退出循环时是 L == R,有时需要再验证 L / R 处是否真的满足要求。带等号的 if 判断条件里,边界指针要收缩到 mid,不能是 mid - 1 或 mid  + 1。

所以在大多数情况下,最简单的套路方法还是【前瞄法】和【后瞄法】,能用这两种时,绝不用区间排除法,除非由于某种条件限制无法准确获得前一个或后一个元素。  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1088611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ROS 摄像头标定-camera_calibration

摄像头这种精密仪器对光学器件的要求较高&#xff0c;由于摄像头内部与外部的一些原因&#xff0c;生成的物体图像往往会发生畸变&#xff0c;为了避免数据源造成的误差&#xff0c;需要针对摄像头的参数进行标定。 ROS官方提供了用于双目和单目摄像头标定的功能包—camera_cal…

单链表---结构体实现

定义 链表称为线性表的链式存储&#xff0c;顺序表逻辑上相邻的数据&#xff0c;存储位置也相邻。链表逻辑上相邻的数据&#xff0c;存储位置是随机分布在内存的各个位置上的。 故 对于每一个结点&#xff0c;定义的结构体是&#xff1a; typedef struct _LinkNode {int d…

存档&改造【06】Apex-Fancy-Tree-Select花式树的使用误删页数据还原(根据时间节点导出导入)

之前一直想实现厂区-区域-产线之间的级联选取&#xff0c;于是导入插件Apex-Fancy-Tree-Select花式树 存档&#xff06;改造【03】Apex-Fancy-Tree-Select花式树的导入-CSDN博客 现在则是在Oracle Apex中的应用 花式书级联列表展示厂区-区域-产线 想要实现的效果 由厂区>…

Shopee活动报错怎么办?Shopee活动类型怎么选择?-站斧浏览器

有时候&#xff0c;在同步虾皮活动的过程中&#xff0c;您可能会遇到一些问题或报错信息。遇到这种情况要怎么办&#xff0c;Shopee活动类型怎么选择&#xff1f; Shopee活动报错怎么办&#xff1f; 下面是几种常见的问题及其解决方法&#xff1a; 1、网络连接问题&#xff…

05 | @Query 解决了什么问题?什么时候应该选择它?

上个课时我们介绍了 Query Define Method 的语法&#xff0c;这一课时来介绍一下 Query 注解的语法是什么样的。我们通过快速体验 Query 的方法、JpaQueryLookupStrategy 关键源码剖析、Query 的基本用法、Query 之 Projections 应用返回指定 DTO、Query 动态查询解决方法&…

网工笔记整理:策略工具Filter-policy的使用

一、概述 Filter-Policy&#xff08;过滤-策略&#xff09;是一个很常用的路由信息过滤工具&#xff0c;能够对接收、发布、引入的路由进行过滤&#xff0c;可应用于IS-IS、OSPF、BGP等协议。 Filter-policy在距离矢量路由协议中的应用 filter-policy import&#xff1a;不发…

Python学习基础笔记六十五——布尔值

布尔对象&#xff1a; Python中有一种对象类型称之为布尔对象&#xff08;英文叫bool&#xff09;。 布尔对象只有两种取值&#xff0c;True和False。对应的是真和假&#xff0c;或者说是和否。True对应的是&#xff0c;False对应的是否。 我觉得这句话是一个关键&#xff1a…

04 | 如何利用 Repository 中的方法返回值解决实际问题?

上一课时&#xff0c;我们着重讲了方法名和参数的使用方法&#xff0c;这一课时我们来看下Repository 支持的返回结果有哪些&#xff0c;以及 DTO 类型的返回结果如何自定义&#xff0c;及其在实际工作场景中我们如何做。通过本课时的学习&#xff0c;你将了解到 Repository 的…

Ansible概述以及模块

目录 一、Ansible概述 1、Ansible是什么 2、Ansible的作用 3、Ansible的特性 4、Ansible的工作机制 5、Ansible的特点 二、Ansible安装部署 1、管理端安装ansible 2、配置主机清单 3、配置密钥对验证 三、Absible命令行模块 1、command模块 2、shell模块 3、cron …

Jackson+Feign反序列化问题排查

概述 本文记录在使用Spring Cloud微服务开发时遇到的一个反序列化问题&#xff0c;RPC/HTTP框架使用的是Feign&#xff0c;JSON序列化反序列化工具是Jackson。 问题 测试环境的ELK告警日志如下&#xff1a; - [43f42bf7] 500 Server Error for HTTP POST "/api/open/d…

一篇文章让你了解“JWT“

一.JWT简介 1.概念 JWT (JSON Web Token) 是一种用于在网络上安全传输信息的开放标准&#xff08;RFC 7519&#xff09;。它是一种紧凑且自包含的方式&#xff0c;用于在不同组件之间传递信息&#xff0c;通常用于身份验证和授权目的。JWT 是以 JSON 格式编码的令牌&#xff…

Shell后门脚本

说明&#xff1a;请在云服务器中执行&#xff0c;执行脚本后会发生 1、创建${create_user}用户并赋予sudo权限2、获取公网IP地址和SSH端口3、将用户公网IPSSH端口信息发送给${mail_from}变量 #!/bin/bash # 收件人邮箱 mail_fromxxxxx.com sshd_port$(grep "^Port"…

AI低代码维格云甘特视图怎么用?

甘特视图,以日期为横轴展示任务持续时长和先后顺序,简称甘特图。 项目管理过程中,合理分配任务和资源至关重要,使用甘特图,妥当解决以下场景: 想知道整个项目的周期多长,哪些任务对项目的周期影响最大; 想知道每个任务的时间有多长,任务的优先级和依赖关系是什么; 想…

VUE3页面截取部署后的二级目录地址

用vue3开发了一个项目&#xff0c;只能部署在根目录&#xff0c;不能加二级目录&#xff0c;后来网上找了解决方案&#xff0c;在vite.config.ts中增加base: ./,配置解决问题&#xff0c;参考下图&#xff1a; 但部署后要获取部署的二级目录地址切遇到问题&#xff0c;后来想了…

SQL如何导入数据以及第一次上机作业

如何导入excel数据 首先得学会导入数据 使用excel格式不需要改成其它格式&#xff08;如csv&#xff0c;txt&#xff09;&#xff0c;因为你改了到时候还是会报错&#xff08;实践过使用Sum统计总数一直说我数据格式有问题&#xff09; 首先右键TSGL数据库->任务->导入数…

makefile规则查找

文章目录 对应 跟我一起写Makefile2.1**makefile** **的规则****2.2** **一个示例****2.5** **让** **make** **自动推导****2.6 makefile** **的另一种风格****2.7** **清空目录的规则****2.10** **包含其它** **Makefile****3.4** **文件搜寻****3.5** **伪目标****3.6** **多…

AWS香港Web3方案日,防御云安全实践案例受关注

9月26日&#xff0c;AWS合作伙伴之Web3解决方案日在香港举办。来自人工智能、Web3等领域的创业公司、技术专家、风险投资商&#xff0c;就元宇宙时代未来发展进行了深入交流。现场展示了顶象防御云在金融与Web3领域的安全实践案例。 Web3为互联网体系架构的一个整体演进和升级&…

LoRa技术未来发展前景:物联网和边缘计算的引领者

随着物联网和边缘计算的快速发展&#xff0c;低功耗广域网&#xff08;LoRa&#xff09;技术在连接远距离设备、实现长距离通信和满足低功耗需求方面崭露头角。本文将分析LoRa技术在未来的发展前景&#xff0c;尤其是在物联网和边缘计算领域的潜在影响。 LoRa技术的核心优势 1…

自动化产线集控系统(西门子CNC 840D/840DSL远程控制)

1.1项目背景 RQQ/VF120机组目前为1人操作3台机床&#xff0c;需在机台旁监控。为了改善人员在班中劳动强度非常大的现状&#xff0c;调整好每台机床的节奏&#xff0c;以保证机床的最少的等待时间。本项目旨在通过远程监视设备运行过程关键参数&#xff0c;操作人员人员可远程监…

清洁洗鞋商城小程序的作用是什么

人靠衣装&#xff0c;一身干净合身的衣物总是给人赏心悦目的感觉&#xff0c;人们对颜值要求越来越高&#xff0c;不仅是衣服&#xff0c;鞋也是重要的组成部分。各种品牌样式鞋&#xff0c;很多人家里往往有几十双&#xff0c;而在清洁这一块&#xff0c;没有时间、或材质特殊…