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:
2 <= len(nums) <= 10000
.0 <= nums[i] < 1000000
.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 Matrix 和 Kth 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; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/719
類似題目:
Find K Pairs with Smallest Sums
Kth Smallest Element in a Sorted Matrix
Kth Smallest Number in Multiplication Table
參考資料:
https://leetcode.com/problems/find-k-th-smallest-pair-distance/solution/