【问题标题】:Find Kth Smallest Pair Distance - Analysis查找第 K 个最小的对距离 - 分析
【发布时间】:2018-02-11 19:16:07
【问题描述】:

问题:

这是 LeetCode 的一个问题:

给定一个整数数组,返回所有数组中第 k 个最小的距离 对。一对 (A, B) 的距离定义为绝对 A 和 B 的区别。

例子:

Input:
nums = [1,3,1]
k = 1
Output: 0 
Explanation:
Here are all the pairs:
(1,3) -> 2
(1,1) -> 0
(3,1) -> 2
Then the 1st smallest distance pair is (1,1), and its distance is 0.

我的问题

我用一种天真的方法 O(n^2) 解决了它,基本上我找到所有距离并对其进行排序,然后找到第 k 个最小的。 现在这是一个更好的解决方案。这不是我的代码,我是在 leetcode 的论坛上找到的。但我无法理解代码的关键部分。

下面的代码基本上是进行二分查找。 low 是最小距离,high 是最大距离。像通常的二进制搜索一样计算mid。然后它会 countPairs(a, mid) 找到绝对差小于或等于 mid 的对数。然后相应地调整lowhigh

但为什么二分搜索结果必须是距离之一? 首先,lowhigh 是从数组中得到的,但 mid 是由它们计算出来的,可能不是距离。最后,我们返回low,其值在基于mid + 1 的二进制搜索期间发生变化。为什么mid + 1保证是距离之一?

class Solution {
    // Returns index of first index of element which is greater than key
    private int upperBound(int[] a, int low, int high, int key) {
        if (a[high] <= key) return high + 1;
        while (low < high) {
            int mid = low + (high - low) / 2;
            if (key >= a[mid]) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return low;
    }

    // Returns number of pairs with absolute difference less than or equal to mid.
    private int countPairs(int[] a, int mid) {
        int n = a.length, res = 0;
        for (int i = 0; i < n; i++) {
            res += upperBound(a, i, n - 1, a[i] + mid) - i - 1;
        }
        return res;
    }

    public int smallestDistancePair(int a[], int k) {
        int n = a.length;
        Arrays.sort(a);

        // Minimum absolute difference
        int low = a[1] - a[0];
        for (int i = 1; i < n - 1; i++)
            low = Math.min(low, a[i + 1] - a[i]);

        // Maximum absolute difference
        int high = a[n - 1] - a[0];

        // Do binary search for k-th absolute difference
        while (low < high) {
            countPairs(a, mid)
            if (countPairs(a, mid) < k)
                low = mid + 1;
            else
                high = mid;
        }

        return low;
    }
}

【问题讨论】:

    标签: algorithm sorting binary-search


    【解决方案1】:

    这种类型的二分搜索将找到 first countPairs(a,x) >= k 的值 x。 (topcoder tutorial 很好地解释了这一点。)

    因此当函数以最终值low终止时,我们知道当距离从low-1变为low时,pair的数量会发生变化,因此必然存在距离low的pair。

    例如,假设我们的目标是 100,并且知道:

    countPairs(a,9) = 99
    countPairs(a,10) = 100
    

    必须有一对距离正好为 10 的数字,因为如果没有这样的数字对,那么距离小于或等于 10 的数字对的数量将与距离小于或等于 10 的数字对的数量相同等于 9。

    请注意,这只适用于循环一直运行到被测间隔完全耗尽的情况。如果代码使用了提前终止条件,如果找到确切的目标值则退出循环,那么它可能会返回错误的答案。

    【讨论】:

    • 我知道这种类型的二分搜索会找到 countPairs(a,x) >= k 的第一个值 X。 (我们只在 countPairs(a, mid)
    • 我已经添加了一个例子,它可能有助于考虑尝试构建一个反例。
    • 您的示例很有帮助,我理解它是因为 countPairs(a,x) 和 countPairs(a,x + 1) 相差 1,这似乎不像我们正在跳跃的二进制 serach 的情况一次超过 1 个。按照你的例子。说k= 100lo = 0hi = 18。我们有mid = 9。因为countPairs(a,9) = 99 &lt; 100,我们将更新lo = mid + 1。现在我们有k = 100lo = 10hi = 18。下次我们搜索时,我们正在查看 countPairs(a,14) 而不是 countPairs(a,10)
    • 我同意你所说的一切,但你需要进行更多的迭代。我们将查看 countPairs(a,14) 并将 high 设置为 14。然后我们将查看 countPairs(a,12) 并将 high 设置为 12。然后我们将查看 countPairs(a,11) 并将 high 设置为 11。最后,我们将查看 countPairs(a,10) 并将 high 设置为 10。关键是二进制搜索会找到 countPairs(a,x) 在目标处的第一个位置,在您的示例中将是当 x 相等时到 10 点。
    • 啊,我看到了 lo 和 hi 最终会分开 1。那时中间将是lo。所以我们将检查 countPairs(a,lo) = x。如果 x 小于 k,我们将返回 mid + 1,这是最后一次迭代中的 hi。因为我们从上次迭代中知道 countPairs(a,lo) = k 对吧?我想我几乎明白了?但是我们怎么知道 lo 和 hi 在哪里相​​遇,最后的 mid + 1 值是保证是一对创建的距离? (我想我在输入这一段时感到困惑,我以为我理解了一会儿)
    【解决方案2】:

    出于兴趣,我们可以在O(n log n + m log m) 时间内解决这个问题,其中m 是范围,使用快速傅里叶变换。

    首先对输入进行排序。现在考虑数字之间的每个可达到的距离都可以通过从另一个中减去一个差异前缀和来实现。例如:

    input:            1 3 7
    diff-prefix-sums:  2 6
    difference between 7 and 3 is 6 - 2
    

    现在让我们将总和(最右边的前缀和)添加到等式的每一边:

    ps[r] - ps[l]       = D
    ps[r] + (T - ps[l]) = D + T
    

    让我们列出不同之处:

    1 1 3
     0 2
    

    和前缀总和:

    p     => 0 0 2
    T - p => 2 2 0  // 2-0, 2-0, 2-2
    

    我们需要有效地确定和排序所有不同的可实现差异的计数。这类似于将带有系数的多项式[1, 0, 2] 乘以带有系数的多项式[2, 0, 0](我们不需要第二组中的零系数,因为它只生成小于或等于T 的度数),这我们可以在m log m 时间内完成,其中m 是度数,使用快速傅里叶变换。

    得到的系数是:

      1 0 2
    * 
      2 0 0
    => 
      x^2 + 2
    *
      2x^2
    
    = 2x^4 + 4x^2
    
    => 2 0 4 0 0
    

    我们丢弃低于T 的度数,并显示我们的排序结果:

    2 * 4 = 2 * (T + 2) => 2 diffs of 2
    4 * 2 = 4 * (T + 0) => 4 diffs of 0
    

    我们多算了 0 的差异。也许有一种方便的方法可以计算有人建议的零多算。我花了一些时间,但还没有区分。

    在任何情况下,使用不相交的重复计数很容易获得零差异的计数,这使我们仍然可以返回 O(n log n + m log m) 总时间中的 kth 差异。

    【讨论】:

      猜你喜欢
      • 2020-12-24
      • 1970-01-01
      • 2012-06-11
      • 2013-02-02
      • 2012-10-22
      • 2013-01-28
      • 2013-12-06
      • 1970-01-01
      • 2020-06-14
      相关资源
      最近更新 更多