1. 程式人生 > >[LeetCode] Minimize Max Distance to Gas Station 最小化去加油站的最大距離

[LeetCode] Minimize Max Distance to Gas Station 最小化去加油站的最大距離

On a horizontal number line, we have gas stations at positions stations[0], stations[1], ..., stations[N-1], where N = stations.length.

Now, we add K more gas stations so that D, the maximum distance between adjacent gas stations, is minimized.

Return the smallest possible value of D.

Example:

Input: stations = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], K = 9
Output: 0.500000

Note:

  1. stations.length will be an integer in range [10, 2000].
  2. stations[i] will be an integer in range [0, 10^8].
  3. K will be an integer in range [1, 10^6].
  4. Answers within 10^-6 of the true value will be accepted as correct.

這道題說給了我們n個加油站,兩兩之間相距不同的距離,然後我們可以在任意地方新加K個加油站,問能使得任意兩個加油站之間的最大距離的最小值是多少。乍眼一看,感覺很繞,一會兒最大,一會兒最小的。其實我們可以換個場景,比如n個人站一隊,每兩個人之間距離不同,有的人之間距離可能很大,有的人可能捱得很近。我們現在需要再加入K個人到佇列中,我們希望人與人之間的距離儘可能小,所以新人就應該加入到距離大的地方,然後問我們加入K個人後,求人與人之間的最大距離。這麼一說,是不是清晰一點了呢。博主最開始看到這個加油站的題,以為跟之前那道

Gas Station有關聯,結果發現二者並沒有什麼關係,只不過公用了加油站這個場景而已。對於這道題,我們還是抽離出本質,就是陣列插數問題。博主最先考慮的是用貪婪演算法,就是先算出每兩個數字之間的距離,然後我們每次往距離最大的那兩個數字之間插入一個數字,這種想法看似正確,但是會跪在這樣一個test case:

[10, 19, 25, 27, 56, 63, 70, 87, 96, 97],K = 3

其兩兩之間的距離為:

9,6,2,29,7,7,17,9,1

如果按照博主前面所說的方法,會先將29分開,變成兩個14.5,然後會將17分開,變成兩個8.5,還剩一個加油站,會將其中一個14.5分開,變成兩個7.25。但是這樣弄下來,最大的距離還是14.5,而實際上我們有更好的辦法,我們用兩個加油站將29三等分,會變成三個9.67,然後用剩下的一個去分17,得到兩個8.5,此時最大距離就變成了9.67,這才是最優的解法。這說明了博主那種圖樣圖森破的貪婪演算法並不work,缺乏對Hard題目的尊重。

後來看了官方解答貼中的解法,發現用DP會記憶體超標MLE,用堆會時間超標TLE。其實這道題的正確解法是用二分搜尋法,博主第一反應是,還有這種操作!!??就是有這種操作!!!這道題使用的二分搜尋法是博主歸納總結帖LeetCode Binary Search Summary 二分搜尋法小結中的第四種,即二分法的判定條件不是簡單的大小關係,而是可以抽離出子函式的情況,下面我們來看具體怎麼弄。如果光說要用二分法來做,那麼首先就要明確的是二分法用來查詢什麼,難道是用來查詢要插入加油站的位置嗎?很顯然不是,其實是用來查詢那個最小的任意兩個加油站間的最大距離。這其實跟之前那道Kth Smallest Element in a Sorted Matrix非常的類似,那道題的二分搜尋也是直接去折半定位所求的數,然後再來驗證其是否真的符合題意。這道題也是類似的思路,題目中給了數字的範圍[0, 10^8],那麼二分查詢的左右邊界值就有了,又給了誤差範圍10^-6,那麼只要right和left差值大於這個閾值,就繼續迴圈。我們折半計算出來的mid就是一個candidate,我們要去驗證個candidate是否符合題意。驗證的方法其實也不難,我們計算每兩個加油站之間的距離,如果此距離大於candidate,則計數器累加1,如果大於candidate距離的個數小於等於k,則說明我們的candidate偏大了,那麼right賦值為mid;反之若大於candidate距離的個數大於k,則說明我們的candidate偏小了,那麼left賦值為mid。最後left和right都會收斂為所要求的最小的任意兩個加油站間的最大距離,是不是很神奇呀!!Amazing!!參見程式碼如下:

解法一:

class Solution {
public:
    double minmaxGasDist(vector<int>& stations, int K) {
        double left = 0, right = 1e8;
        while (right - left > 1e-6) {
            double mid = left + (right - left) / 2;
            if (helper(stations, K, mid)) right = mid;
            else left = mid;
        }
        return left;
    }
    bool helper(vector<int>& stations, int K, double mid) {
        int cnt = 0, n = stations.size();
        for (int i = 0; i < n - 1; ++i) {
            cnt += (stations[i + 1] - stations[i]) / mid;
        }
        return cnt <= K;
    }
};

我們也可以把上面解法中的子函式揉到主函式裡面,這樣可以是的程式碼更加的簡潔,參見程式碼如下:

解法二:

class Solution {
public:
    double minmaxGasDist(vector<int>& stations, int K) {
        double left = 0, right = 1e8;
        while (right - left > 1e-6) {
            double mid = left + (right - left) / 2;
            int cnt = 0, n = stations.size();
            for (int i = 0; i < n - 1; ++i) {
                cnt += (stations[i + 1] - stations[i]) / mid;
            }
            if (cnt <= K) right = mid;
            else left = mid;
        }
        return left;
    }
};

類似題目:

參考資料: