巧用快排,秒殺 LeetCode 973. K Closest Points to Origin

細節決定成敗。一個小細節可讓代碼的性能大幅提高。java

最近和朋友一塊兒研究分治算法,看過《算法導論》後以爲,紙上得來終覺淺,絕知此事要躬行啊!遂去 LeetCode 上 Divide and Conquer 這個 Topic 下,作了這道題 973. K Closest Points to Origin算法

本文分享做者在作題時發現的優秀代碼細節,但願和你們一塊兒吸收養分並體會其中樂趣。bash

閱讀前建議你們先本身作一下該題,至少思考一下,而後再往下閱讀,這樣更容易體會到這個細節的優雅。ide

1、題目

973. K Closest Points to Origin性能

We have a list of points on the plane.  Find the K closest points to the origin (0, 0).

(Here, the distance between two points on a plane is the Euclidean distance.)

You may return the answer in any order.  The answer is guaranteed to be unique (except for the order that it is in.)

Example 1:

Input: points = [[1,3],[-2,2]], K = 1
Output: [[-2,2]]
Explanation: 
The distance between (1, 3) and the origin is sqrt(10).
The distance between (-2, 2) and the origin is sqrt(8).
Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin.
We only want the closest K = 1 points from the origin, so the answer is just [[-2,2]].

Example 2:

Input: points = [[3,3],[5,-1],[-2,4]], K = 2
Output: [[3,3],[-2,4]]
(The answer [[-2,4],[3,3]] would also be accepted.)
 
Note:
1 <= K <= points.length <= 10000
-10000 < points[i][0] < 10000
-10000 < points[i][1] < 10000

複製代碼

題目大意就是讓找出距離原點最近的前K個點。而且無需關心結果的順序ui

2、代碼

閒言少敘,直接上改進前的代碼:idea

class Solution {
    public int[][] kClosest(int[][] points, int K) {
        quickSort(points, 0, points.length - 1);
        return Arrays.copyOfRange(points, 0, K);
    }
    
    private void quickSort(int[][] a, int low, int high) {
        if(low < high) {
            int pivot = partation(a, low, high);
            quickSort(a, low, pivot - 1);
            quickSort(a, pivot + 1, high);
        }
    }
    
    private int partation(int[][] a, int low, int high) {
        int[] pivot = a[low];
        int pivotDist = dist(pivot);
        
        while(low < high) {
            while(low < high && dist(a[high]) >= pivotDist) {
                high--;
            }
            a[low] = a[high];
            while(low < high && dist(a[low]) <= pivotDist) {
                low++;
            }
            a[high] = a[low];
        }
        a[low] = pivot;
        return low;
    }

    private int dist(int[] a) {
        return a[0] * a[0] + a[1] * a[1];
    }
}

複製代碼

用時16ms, 反覆試了幾回也都是10+ms,看到 LeetCode 記錄用時最短的方案是 3ms,把這個3ms的方案提交一下,如今用時4ms,大概看了一下,他也是用快速排序,和個人代碼幾乎同樣,可是個人爲何這麼慢?spa

3、找差距,變優秀

差距主要在 quickSort 方法,他的寫法相似下面,加了一些判斷,去掉了一些沒必要要的排序,由於題目說 "You may return the answer in any order", 因此只需找到前K個最小的便可,無需保證前K個按照從小到大的順序。code

private void quickSort(int[][] a, int low, int high, int K) {
        if(low < high) {
            int pivot = partation(a, low, high);
            if(pivot == K) return;
            if(pivot > K) {
                quickSort(a, low, pivot - 1, K);
            }else{
                quickSort(a, pivot + 1, high, K);
            }
        }
    }

複製代碼

改爲這樣後用時也是4ms. 我對這段代碼的理解:
通過一趟快排後,樞軸(支點)的左側都小於等於它,右側都大於等於它。
(1)若是樞軸的下標等於K, 則說明樞軸左側的K個點便是前K個距原點距離最小的點,返回便可;
(2)若是樞軸的下標大於K, 則要想知足(1),只需將 low 至 pivot - 1 繼續進行快速排序;
(3)若是樞軸的下標小於K, 則要想知足(1),只需將 pivot + 1 至 high 繼續進行快速排序。排序

因而可知思路上的差別,我以前的思路是利用快速排序進行從小到大排序,而後取前K個,而這種作法是找恰當的樞軸,樞軸左側的點便是要找的點,省去不少無用功,將快速排序用的恰到好處。

4、總結

解決問題時,要注意題目中的條件,巧用適當的算法。找到與優秀的差距,變成優秀。

相關文章
相關標籤/搜索