【LeetCode刷题笔记】二分查找

本文详细介绍了如何在LeetCode的多种问题中应用二分查找,包括寻找缺失的正整数、错误的版本、数组中的峰值、旋转排序数组中的元素等。文章列举了多种二分查找的变体,如前瞄法、区间排除法、后瞄法,并总结了二分查找的基本形式和变种形式,强调了在不同场景下选择合适二分策略的重要性。此外,还特别强调了隐藏查找区间的问题,指出需要从题目中抽象出实际的二分查找区间,并提供了相关代码模板和注意事项。
摘要由CSDN通过智能技术生成

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 和是多少:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值