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)。ui
算法實現類spa
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; } }