當(dāng)老司機學(xué)會了貪心算法 ??
時間:2021-10-08 16:44:19
手機看文章
掃描二維碼
隨時隨地手機看文章
[導(dǎo)讀]讀完本文,可以去力扣解決如下題目:134.加油站(Medium)今天講一個貪心的老司機的故事,就是力扣第134題「加油站」:題目應(yīng)該不難理解,就是每到達(dá)一個站點i,可以加gas[i]升油,但離開站點i需要消耗cost[i]升油,問你從哪個站點出發(fā),可以兜一圈回來。要說暴力解法,肯...
讀完本文,可以去力扣解決如下題目:134.加油站(Medium)今天講一個貪心的老司機的故事,就是力扣第 134 題「加油站」:
題目應(yīng)該不難理解,就是每到達(dá)一個站點
題目應(yīng)該不難理解,就是每到達(dá)一個站點
i
,可以加gas[i]
升油,但離開站點i
需要消耗cost[i]
升油,問你從哪個站點出發(fā),可以兜一圈回來。要說暴力解法,肯定很容易想到,用一個 for 循環(huán)遍歷所有站點,假設(shè)為起點,然后再套一層 for 循環(huán),判斷一下是否能夠轉(zhuǎn)一圈回到起點:int?n?=?gas.length;
for?(int?start?=?0;?start?????for?(int?step?=?0;?step?????????int?i?=?(start? ?step)?%?n;
????????tank? =?gas[i];
????????tank?-=?cost[i];
????????//?判斷油箱中的油是否耗盡
????}
}
很明顯時間復(fù)雜度是 O(N^2),這么簡單粗暴的解法一定不是最優(yōu)的,我們試圖分析一下是否有優(yōu)化的余地。暴力解法是否有重復(fù)計算的部分?是否可以抽象出「狀態(tài)」,是否對同一個「狀態(tài)」重復(fù)計算了多次?以前講動態(tài)規(guī)劃算法時候,我曾經(jīng)說過,變化的量就是「狀態(tài)」。那么觀察這個暴力窮舉的過程,變化的量有兩個,分別是「起點」和「當(dāng)前油箱的油量」,但這兩個狀態(tài)的組合肯定有不下 O(N^2) 種,顯然沒有任何優(yōu)化的空間所以說這道題肯定不是通過簡單的剪枝來優(yōu)化暴力解法的效率,而是需要我們發(fā)現(xiàn)一些隱藏較深的規(guī)律,從而減少一些冗余的計算。下面我們介紹兩種方法巧解這道題,分別是數(shù)學(xué)圖像解法和貪心解法。圖像解法
汽車進入站點i
可以加gas[i]
的油,離開站點會損耗cost[i]
的油,那么可以把站點和與其相連的路看做一個整體,將gas[i] - cost[i]
作為經(jīng)過站點i
的油量變化值:這樣,題目描述的場景就被抽象成了一個環(huán)形數(shù)組,數(shù)組中的第i
個元素就是gas[i] - cost[i]
。有了這個環(huán)形數(shù)組,我們需要判斷這個環(huán)形數(shù)組中是否能夠找到一個起點start
,使得從這個起點開始的累加和一直大于等于 0。如何判斷是否存在這樣一個起點start
?又如何計算這個起點start
的值呢?我們不妨就把 0 作為起點,計算累加和的代碼非常簡單:int?n?=?gas.length,?sum?=?0;
for?(int?i?=?0;?i?????//?計算累加和
????sum? =?gas[i]?-?cost[i];
}
sum
就相當(dāng)于是油箱中油量的變化,上述代碼中sum
的變化過程可能是這樣的:顯然,上圖將 0 作為起點肯定是不行的,因為sum
在變化的過程中小于 0 了,不符合我們「累加和一直大于等于 0」的要求。那如果 0 不能作為起點,誰可以作為起點呢?看圖說話,圖像的最低點最有可能可以作為起點:如果把這個「最低點」作為起點,就是說將這個點作為坐標(biāo)軸原點,就相當(dāng)于把圖像「最大限度」向上平移了。再加上這個數(shù)組是環(huán)形數(shù)組,最低點左側(cè)的圖像可以接到圖像的最右側(cè):這樣,整個圖像都保持在 x 軸以上,所以這個最低點 4,就是題目要求我們找的起點。不過,經(jīng)過平移后圖像一定全部在 x 軸以上嗎?不一定,因為還有無解的情況:如果sum(gas[...]) < sum(cost[...])
,總油量小于總的消耗,那肯定是沒辦法環(huán)游所有站點的。綜上,我們就可以寫出代碼:int?canCompleteCircuit(int[]?gas,?int[]?cost)?{
????int?n?=?gas.length;
????//?相當(dāng)于圖像中的坐標(biāo)點和最低點
????int?sum?=?0,?minSum?=?Integer.MAX_VALUE;
????int?start?=?0;
????for?(int?i?=?0;?i?????????sum? =?gas[i]?-?cost[i];
????????if?(sum?????????????//?經(jīng)過第 i 個站點后,使 sum 到達(dá)新低
????????????//?所以站點?i? ?1?就是最低點(起點)
????????????start?=?i? ?1;
????????????minSum?=?sum;
????????}
????}
????if?(sum?0)?{
????????//?總油量小于總的消耗,無解
????????return?-1;
????}
????//?環(huán)形數(shù)組特性
????return?start?==?n???0?:?start;
}
以上是觀察函數(shù)圖像得出的解法,時間復(fù)雜度為 O(N),比暴力解法的效率高很多。下面我們介紹一種使用貪心思路寫出的解法,和上面這個解法比較相似,不過分析過程不盡相同。貪心解法
用貪心思路解決這道題的關(guān)鍵在于以下這個結(jié)論:如果選擇站點i
作為起點「恰好」無法走到站點j
,那么i
和j
中間的任意站點k
都不可能作為起點。比如說,如果從站點1
出發(fā),走到站點5
時油箱中的油量「恰好」減到了負(fù)數(shù),那么說明站點1
「恰好」無法到達(dá)站點5
;那么你從站點2,3,4
任意一個站點出發(fā)都無法到達(dá)5
,因為到達(dá)站點5
時油箱的油量也必然被減到負(fù)數(shù)。如何證明這個結(jié)論?假設(shè)tank
記錄當(dāng)前油箱中的油量,如果從站點i
出發(fā)(tank = 0
),走到j
時恰好出現(xiàn)tank < 0
的情況,那說明走到i, j
之間的任意站點k
時都滿足tank > 0
,對吧。如果把k
作為起點的話,相當(dāng)于在站點k
時tank = 0
,那走到j
時必然有tank < 0
,也就是說k
肯定不能是起點。拜托,從i
出發(fā)走到k
好歹tank > 0
,都無法達(dá)到j
,現(xiàn)在你還讓tank = 0
了,那更不可能走到j
了對吧。綜上,這個結(jié)論就被證明了。回想一下我們開頭說的暴力解法是怎么做的?如果我發(fā)現(xiàn)從i
出發(fā)無法走到j
,那么顯然i
不可能是起點。現(xiàn)在,我們發(fā)現(xiàn)了一個新規(guī)律,可以推導(dǎo)出什么?如果我發(fā)現(xiàn)從i
出發(fā)無法走到j
,那么i
以及i, j
之間的所有站點都不可能作為起點。看到冗余計算了嗎?看到優(yōu)化的點了嗎?這就是貪心思路的本質(zhì),如果找不到重復(fù)計算,那就通過問題中一些隱藏較深的規(guī)律,來減少冗余計算。根據(jù)這個結(jié)論,就可以寫出如下代碼:int?canCompleteCircuit(int[]?gas,?int[]?cost)?{
????int?n?=?gas.length;
????int?sum?=?0;
????for?(int?i?=?0;?i?????????sum? =?gas[i]?-?cost[i];
????}
????if?(sum?0)?{
????????//?總油量小于總的消耗,無解
????????return?-1;
????}
????//?記錄油箱中的油量
????int?tank?=?0;
????//?記錄起點
????int?start?=?0;
????for?(int?i?=?0;?i?????????tank? =?gas[i]?-?cost[i];
????????if?(tank?0)?{
????????????//?無法從?start?走到?i
????????????//?所以站點?i? ?1?應(yīng)該是起點
????????????tank?=?0;
????????????start?=?i? ?1;
????????}
????}
????return?start?==?n???0?:?start;
}
這個解法的時間復(fù)雜度也是 O(N),和之前圖像法的解題思路有所不同,但代碼非常類似。其實,你可以把這個解法的思路結(jié)合圖像來思考,可以發(fā)現(xiàn)它們本質(zhì)上是一樣的,只是理解方式不同而已。對于這種貪心算法,沒有特別套路化的思維框架,主要還是靠多做題多思考,將題目的場景進行抽象的聯(lián)想,找出隱藏其中的規(guī)律,從而減少計算量,進行效率優(yōu)化。好了,這道題就講到這里,希望對你拓寬思路有幫助。_____________