題目描述
在一條環路上有 N 個加油站,其中第 i 個加油站有汽油 gas[i] 升。
你有一輛油箱容量無限的的汽車,從第 i 個加油站開往第 i+1 個加油站需要消耗汽油 cost[i] 升。你從其中的一個加油站出發,開始時油箱爲空。
如果你可以繞環路行駛一週,則返回出發時加油站的編號,否則返回 -1。
說明:
如果題目有解,該答案即爲唯一答案。
輸入數組均爲非空數組,且長度相同。
輸入數組中的元素均爲非負數。
示例 1:
輸入:
gas = [1,2,3,4,5]
cost = [3,4,5,1,2]
輸出: 3
解釋:
從 3 號加油站(索引爲 3 處)出發,可獲得 4 升汽油。此時油箱有 = 0 + 4 = 4 升汽油
開往 4 號加油站,此時油箱有 4 - 1 + 5 = 8 升汽油
開往 0 號加油站,此時油箱有 8 - 2 + 1 = 7 升汽油
開往 1 號加油站,此時油箱有 7 - 3 + 2 = 6 升汽油
開往 2 號加油站,此時油箱有 6 - 4 + 3 = 5 升汽油
開往 3 號加油站,你需要消耗 5 升汽油,正好足夠你返回到 3 號加油站。
因此,3 可爲起始索引。
示例 2:
輸入:
gas = [2,3,4]
cost = [3,4,3]
輸出: -1
解釋:
你不能從 0 號或 1 號加油站出發,因爲沒有足夠的汽油可以讓你行駛到下一個加油站。
我們從 2 號加油站出發,可以獲得 4 升汽油。 此時油箱有 = 0 + 4 = 4 升汽油
開往 0 號加油站,此時油箱有 4 - 3 + 2 = 3 升汽油
開往 1 號加油站,此時油箱有 3 - 3 + 3 = 3 升汽油
你無法返回 2 號加油站,因爲返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,無論怎樣,你都不可能繞環路行駛一週。
解題思路
暴力解法
最簡單的思路,就是判斷當前索引i對應的gas[i]和cost[i]對應的大小情況。
如果gas[i] >= cost[i],則當前索引i的加油站可以被認爲滿足了1個條件,後續只要從它開始循環,如果能回到索引i,則這個索引是有效的;
否則,索引i+1,重新判斷gas[i]和cost[i]對應的大小情況。
因爲題目中已經給定了答案要麼僅有一個,要麼就沒有。所以如果找到了那個索引i就可以從循環中跳出,下面給出“乞丐版”代碼。其中的註釋很詳細。
“乞丐版”代碼
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int len = gas.size(); // 輸入數組的長度
int tar = -1; // 這是需要返回的加油站索引,tar屬於[0, len-1]
int rest; // 存放油箱的含油量
for (int i = 0; i < len; i++)
{
// 滿足條件說明小車可以從當前加油站出發,接下來開始一個循環,判斷小車是否能持續地回到加油站i
if (gas[i] >= cost[i])
{
int j; // 初始化待循環的變量
rest = gas[j] - cost[j]; // 初始化從加油站i出發時油箱中的油
for (j = (i+1) % len; j != i; j = (j+1) % len)
{
rest += gas[j]; // 小車能從加油站j獲取油後油箱的含油量
rest -= cost[j]; // 小車到達加油站j+1消耗了油之後油箱的含油量
/* 如果郵箱含油量小於0,說明油箱中的油不足以支撐小車從加油站j到加油站j+1,
也就是說以加油站i作爲起點不可行,在途中會遇到加油站j,此時油箱中的油不足以支撐小車從加油站j到加油站j+1 */
if (rest < 0)
break;
}
// 如果j再次等於i,說明以加油站i作爲起點可行,那麼保存i到返回的變量tar中,然後跳出循環
if (i == j)
{
tar = i;
break;
}
}
}
// 如何tar還是-1,則說明遍歷完所有加油站都沒有找到合適的加油站,則返回-1
return (tar == -1 ? -1 : tar);
}
};
// “乞丐版”思路很簡單,但是它最壞的複雜度能達到O(n^(2)),考慮用些trick將複雜度降低。
改進版
問:判斷一個合格的加油站,除了從某個加油站i開始循環,最後回到加油站i這個方法之外,還有沒有其他的辦法?
答:有的,如果小車從所有加油站能獲取的油 >= 跑完所有加油站所消耗的油,那麼能斷定可以找到某個加油站i,從i開始出發,最後能回到i。這個可以用反證法證明。但是如何找到那個加油站i?正因爲題目中給出了一個條件,即這個加油站要麼有且僅存在一個,要麼沒有,所以可以用算法很簡單的實現。
下面給出算法,並在算法中給出詳細註釋
改進版算法
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int len = gas.size(), rest = 0; // rest是小車油箱中剩餘的油,初始化爲0
int tar = 0; // tar存放的是可能合格的加油站索引,初始化爲0
for (int i = 0; i < gas.size(); i++)
{
/* 算法主體:
如果小車油箱中剩餘的油rest加上從加油站i獲取的油能支撐它從加油站i跑到加油站i+1,那沒問題,循環繼續;
如果不足以支撐,那麼當前的加油站i不可能是滿足要求的加油站,將tar賦爲下一個加油站i+1,並且將欠缺的油補到最後的一個加油站的cost。
之所以將欠缺的油補到最後的一個加油站是爲了判斷小車能獲取的所有的油能否大於跑完所有加油站所消耗的油
*/
if (rest + gas[i] < cost[i])
{
tar = i + 1;
cost[len-1] += cost[i] - gas[i] - rest;
rest = 0;
}
else
{
rest += gas[i] - cost[i];
}
}
return (tar == len ? -1 : tar);
}
};