二分搜索:第 K 小问题

719. 找出第 K 小的数对距离

1439. 有序矩阵中的第 k 个最小数组和

 

本篇文章主要介绍如何使用「二分搜索」来解决「第 K 小」的问题。如上面给出的两个题目,难度为「Hard」,在理解上可能需要花更多的时间!!

关于「二分搜索」的详细介绍可见 二分搜索关于抽象类的二分搜索 抽象类的二分搜索

今天的题目也属于「抽象类的二分搜索」类型,所有我们需要搞清楚什么是「搜索对象」「搜索范围」

找出第 K 小的数对距离

题目详情可见 找出第 K 小的数对距离

这里给出暴力的思路:维护一个大小为k的大根堆优先队列,如果入队元素「小于」当前队顶元素,则弹出队顶元素,然后把要入队的元素压入队列中。当处理完所有元素后,队顶元素即为「第 K 小」的元素

当然,本篇文章要介绍的方法更快,更好!其实就是二分搜索啦!!!

我们先明确一下原问题:「返回所有数对距离中k小的数对距离」

对于一个给定的数对距离d,如果该距离近了,那我们就「向右收缩」;如果该距离远了,那我们就「向左收缩」;如果该距离满足要求,那我们能不能找一个更小的且满足要求的距离,所以需要继续「向左收缩」(是不是很像「寻找最左相等元素」?自信点,就是「寻找最左相等元素」)

所以「搜索对象」是什么??很明显就是「距离」嘛!!

「搜索范围」又是什么呢??

明确了「搜索对象」「搜索范围」,我们还需要搞清楚怎么确定距离近了还是远了,肯定要有一个参考对象才能确定近还是远嘛

很聪明,这个参考对象就是题目给的「第k小」。对于距离d,小于等于该距离的数量为n,如果n < k说明距离近了;如果n > k说明距离远了

双指针计算n

对于一个距离d,怎么得到小于等于该距离的数量呢?

可以直接暴力,但这不是我们想要的,这里介绍「双指针」方法。

使用「双指针」的前提是需要数组有序,由于本题目中的距离是绝对值,所以事先对数组排序并不影响结果的正确性

假设nums = [1, 4, 5, 8, 10, 12], d = 4

双指针i表示区间的起点,j表示区间的终点,表示为[i, j),注意是「左闭右开」

区间[i, j)表示对于任意 a 且满足 i<a<j,均有 d(i,a)4。具体如下图所示:

1

解释:对于区间[0, 3),以0为起点的数对有:(0, 1), (0, 2),其距离均小于等于 4

i = 0满足要求的情况处理完后,i向前移动一格,此时j只需要在位置3的基础上继续向前扩张即可。具体如下图所示:

2

可以看到橙色部分是i = 0确定的区域,红色部分是新扩张的区域。由于i向前移动了一格,所以上一步满足要求的区域,此时肯定也满足要求 (有点绕,需要好好理解)

下面给出本题的代码实现:

有序矩阵中的第 k 个最小数组和

题目详情可见 有序矩阵中的第 k 个最小数组和

我们先明确一下原问题:「返回所有可能数组中的第 k 个最小数组和」

对于一个给定的数组和sum,如果该数组和小了,那我们就「向右收缩」;如果该数组和大了,那我们就「向左收缩」;如果该数组和满足要求,那我们能不能找一个更小的且满足要求的数组和,所以需要继续「向左收缩」(是不是很像「寻找最左相等元素」?自信点,就是「寻找最左相等元素」)

所以「搜索对象」是什么??很明显就是「数组和」嘛!!

「搜索范围」又是什么呢??

明确了「搜索对象」「搜索范围」,我们还需要搞清楚怎么确定数组和小了还是大了,肯定要有一个参考对象才能确定近还是远嘛

很聪明,这个参考对象就是题目给的「第k最小」。对于数组和sum,小于等于该数组和的数量为n,如果n < k说明数组和小了;如果n > k说明数组和大了

DFS 计算n

这一部分还挺难理解的,看了好多题解,发现很多人都是一下子给出代码!!

既然这是一个 DFS 问题,肯定也是满足 DFS 套路的。关于 DFS 的详细介绍可见 回溯 (DFS) 算法框架

初始状态为第一列,如下图所示:

6

对于第一行,我们可以选择「不变」,也可以选择「 2 or 3 or 4 or 5」与「1」交换。假如选择了「4」与「1」交换,那么此时的子数组为「4,6,11,16」,和为「37」,如下图所示:

7

对于每一行,都和第一行差不多。为了更清晰的写出 DFS,下面给出「搜索树」

9

下面给出本题的代码实现: