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