1. 程式人生 > >[LeetCode] Find K-th Smallest Pair Distance 找第K小的數對兒距離

[LeetCode] Find K-th Smallest Pair Distance 找第K小的數對兒距離

Given an integer array, return the k-th smallest distance among all the pairs. The distance of a pair (A, B) is defined as the absolute difference between A and B.

Example 1:

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.

Note:

  1. 2 <= len(nums) <= 10000.
  2. 0 <= nums[i] < 1000000.
  3. 1 <= k <= len(nums) * (len(nums) - 1) / 2.

這道題給了我們一個數組,讓我們找第k小的數對兒距離,數對兒距離就是任意兩個數字之間的絕對值差。那麼我們先來考慮最暴力的解法,是不是就是遍歷任意兩個數字,算出其絕對值差,然後將所有距離排序,取第k小的就行了。But,OJ搖著頭說圖樣圖森破。但是我們可以在純暴力搜尋的基礎上做些優化,從而讓OJ說YES。那麼下面這種利用了桶排序的解法就是一種很好的優化,題目中給了數字的大小範圍,不會超過一百萬,所以我們就建立一百萬個桶,然後還是遍歷任意兩個數字,將計算出的距離放到對應的桶中,這裡桶不是存的具體距離,而是該距離出現的次數,桶本身的位置就是距離,所以我們才建立了一百萬個桶。然後我們就可以從0開始遍歷到一百萬了,這樣保證了我們先處理小距離,如果某個距離的出現次數大於等於k了,那麼我們返回這個距離,否則就用k減去這個距離的出現次數,參見程式碼如下:

解法一:

class Solution {
public:
    int smallestDistancePair(vector<int>& nums, int k) {
        int n = nums.size(), N = 1000000;
        vector<int> cnt(N, 0);
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                ++cnt[abs(nums[i] - nums[j])];
            }
        }
        
for (int i = 0; i < N; ++i) { if (cnt[i] >= k) return i; k -= cnt[i]; } return -1; } };

上面的解法雖然逃脫了OJ的魔掌,但也僅僅是險過,並不高效。我們來看一種基於二分搜尋的解法。這道題使用的二分搜尋法是博主歸納總結帖LeetCode Binary Search Summary 二分搜尋法小結中的第四種,即二分法的判定條件不是簡單的大小關係,而是可以抽離出子函式的情況,下面我們來看具體怎麼弄。我們的目標是快速定位出第k小的距離,那麼很適合用二分法來快速的縮小查詢範圍,然而最大的難點就是如何找到判定依據來折半查詢,即如果確定搜尋目標是在左半邊還是右半邊。做過Kth Smallest Element in a Sorted MatrixKth Smallest Number in Multiplication Table這兩道題的同學應該對這種搜尋方式並不陌生。核心思想是二分確定一箇中間數,然後找到所有小於等於這個中間數的距離個數,用其跟k比較來確定折半的方向。具體的操作是,我們首先要給陣列排序,二分搜尋的起始left為0,結束位置right為最大距離,即排序後的數字最後一個元素減去首元素。然後進入while迴圈,算出中間值mid,此外我們還需要兩個變數cnt和start,其中cnt是記錄小於等於mid的距離個數,start是較小數字的位置,均初始化為0,然後我們遍歷整個陣列,先進行while迴圈,如果start未越界,並且當前數字減去start指向的陣列之差大於mid,說明此時距離太大了,我們增加減數大小,通過將start右移一個,那麼while迴圈退出後,就有i - start個距離小於等於mid,將其加入cnt中,舉個栗子來說:

   1    2    3    3    5

start              i

mid = 2

如果start在位置0,i在位置3,那麼以nums[i]為較大數可以產生三個(i - start)小於等於mid的距離,[1 3], [2 3], [3 3],這樣當i遍歷完所有的數字後,所有小於等於mid的距離的個數就求出來了,即cnt。然後我們跟k比較,如果其小於k,那麼left賦值為mid+1,反之,則right賦值為mid。最終返回right或left均可,參見程式碼如下:

解法二:

class Solution {
public:
    int smallestDistancePair(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());
        int n = nums.size(), left = 0, right = nums.back() - nums[0];
        while (left < right) {
            int mid = left + (right - left) / 2, cnt = 0, start = 0;
            for (int i = 0; i < n; ++i) {
                while (start < n && nums[i] - nums[start] > mid) ++start;
                cnt += i - start;
            }
            if (cnt < k) left = mid + 1;
            else right = mid;
        }
        return right;
    }
};

類似題目:

K-th Smallest Prime Fraction

參考資料: