1. 程式人生 > >【LeetCode-面試演算法經典-Java實現】【134-Gas Station(加油站問題)】

【LeetCode-面試演算法經典-Java實現】【134-Gas Station(加油站問題)】

原題

  There are N gas stations along a circular route, where the amount of gas at station i is gas[i].
  You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from station i to its next station (i+1). You begin the journey with an empty tank at one of the gas stations.
Return the starting gas station’s index if you can travel around the circuit once, otherwise return -1.
  Note:


  The solution is guaranteed to be unique.

題目大意

  沿環形路線有N個加油站,其中氣體在車站i是量是gas[i]。你有車有無限容量的氣罐,從加油站i到下一個加油站站點i+1,要消耗cost[i]的氣體。你開始旅程時,氣罐是空的。回到起始加油站的指數,選擇一個起點開始旅遊,如果你能在周圍環形旅行一次,就返回開始的加油站索引,否則返回-1。
  注意: 答案保證是唯一的。

解題思路

  假設從站點 i 出發,到達站點 k 之前,依然能保證油箱裡油沒見底兒,從k 出發後,見底兒了。那麼就說明 diff[i] + diff[i+1] + … + diff[k] < 0,而除掉diff[k]以外,從diff[i]開始的累加都是 >= 0的。也就是說diff[i] 也是 >= 0的,這個時候我們還有必要從站點 i + 1嘗試嗎?仔細一想就知道:車要是從站點 i+1出發,到達站點k後,甚至還沒到站點k,油箱就見底兒了,因為少加了站點 i 的油。。。
  因此,當我們發現到達k 站點郵箱見底兒後,i 到 k 這些站點都不用作為出發點來試驗了,肯定不滿足條件,只需要從k+1站點嘗試即可!因此解法時間複雜度從O(n2)降到了 O(2n)。之所以是O(2n),是因為將k+1站作為始發站,車得繞圈開回k,來驗證k+1是否滿足。
  等等,真的需要這樣嗎?
  我們模擬一下過程:
  a. 最開始,站點0是始發站,假設車開出站點p後,油箱空了,假設sum1 = diff[0] +diff[1] + … + diff[p],可知sum1 < 0;
  b. 根據上面的論述,我們將p+1作為始發站,開出q站後,油箱又空了, 設sum2 = diff[p+1] +diff[p+2] + … + diff[q],可知sum2 < 0。
  c. 將q+1作為始發站,假設一直開到了未迴圈的最末站,油箱沒見底兒,設sum3 = diff[q+1] +diff[q+2] + … + diff[size-1],可知sum3 >= 0。
  要想知道車能否開回 q 站,其實就是在sum3 的基礎上,依次加上 diff[0] 到 diff[q],看看sum3在這個過程中是否會小於0。但是我們之前已經知道 diff[0] 到 diff[p-1] 這段路,油箱能一直保持非負,因此我們只要算算sum3 + sum1是否 <0,就知道能不能開到 p+1站了。
  如果能從p+1站開出,只要算算sum3 + sum1 + sum2 是否 < 0,就知都能不能開回q站了。
  因為 sum1, sum2 都 < 0,因此如果 sum3 + sum1 + sum2 >=0 那麼sum3 + sum1 必然 >= 0,也就是說,只要sum3 + sum1 + sum2 >=0,車必然能開回q站。而sum3 + sum1 + sum2 其實就是 diff陣列的總和 Total,遍歷完所有元素已經算出來了。
  因此 Total 能否 >= 0,就是是否存在這樣的站點的 充分必要條件。
  這樣時間複雜度進一步從O(2n)降到了 O(n)。

程式碼實現

演算法實現類

public class Solution {

    public int canCompleteCircuit(int[] gas, int[] cost) {
        // 引數檢驗
        if (gas == null || cost == null || gas.length == 0 || gas.length != cost.length) {
            return -1;
        }

        // 記錄訪問的起始點
        int start = 0;
        // 加的氣和消耗的氣的總差值
int total = 0; // 從start位置開始,加的氣和消耗的氣的總差值 int sum = 0; for (int i = 0; i < gas.length; i++) { total += (gas[i] - cost[i]); // 如是油箱沒有油了 if (sum < 0) { // 重新設定油箱中的油 sum = gas[i] - cost[i]; // 記錄新的起點位置 start = i; } else { // 油箱中還有油,更新油箱中的油數 sum += (gas[i] - cost[i]); } } return total >= 0 ? start : -1; } // 下面的方法會超時O(N^2)時間複雜度 public int canCompleteCircuit2(int[] gas, int[] cost) { // 引數檢驗 if (gas == null || cost == null || gas.length == 0 || gas.length != cost.length) { return -1; } // 剩下的氣體,開始時為0 int leftGas = 0; // 開始出發的站點 int start = 0; // 結束的站點 int end = 1; // 未走一週 while (start < gas.length) { // 到達下一個站後的氣體簡便量 leftGas = gas[start] - cost[start]; // 可以走到下一個站 if (leftGas > 0) { // 記錄下一個站 end = (start + 1) % gas.length; // 如果一直可以到下一個站就持續進行操作 while (start != end && (leftGas += (gas[end] - cost[end])) >= 0) { end = (end + 1) % gas.length; } // 說明已經遍歷了一週 if (start == end) { return start; } } start++; } return -1; } }

評測結果

  點選圖片,滑鼠不釋放,拖動一段位置,釋放後在新的視窗中檢視完整圖片。

這裡寫圖片描述

特別說明