詳細**可以fork下github上leetcode專案,不定期更新。題目摘自leetcode:
1. leetcode 502: ipo
2. leetcode 055: jump game
3. leetcode 330: patching array
刷完挑戰,繼續刷leetcode,遇到的第乙個題就是ipo,而這恰巧是貪心系列,那就順便把貪心給學了,多門技術,多條生路。
《演算法導論》也有關於貪心演算法的相關章節,但我還不敢看,無非怕被書中的論述思維給限制住了。所以先刷點題,對貪心有了基本了解後,回過頭來再看它的論證。
我所理解的貪心:
貪心,每一步決策都是區域性最優的?一種短視的行為?好吧,這是我對貪心最真切的認識了,沒有其他。日後,刷題時逐一完善加深對貪心的理解,話不多說,直接開始。
求n個專案所能累加的最大profit。呵呵噠,leetcode的題目有個很大的特色,很多題在解釋中把思路明確告訴你了,我一開始就納悶了,直接找符合capital中的最大profit累加即可?好像也符合貪心的策略,選擇區域性最優。
思路:
沒錯,按照題目的意思來就可以了,**如下:
public
intfindmaximizedcapital(int k, int w, int profits, int capital) }}
if (index == -1) return total;
capital[index] = integer.max_value;
total += maxprofit;
}return total;
}
tle了,如果有n個專案,那麼上述**時間複雜度為o(
n2) 。嘿,其實在它貪心的背後,它是一道資料結構題,用到了優先佇列。
提到優先佇列,相信你能很快想出了思路,但這對我來說,不夠完美,如果不提優先佇列這想法,我就很難想到用這資料結構了。所以,我慢慢分析下為啥用到了優先佇列。
之前的博文中,我有提過所有的迴圈遍歷,如果沒有容器記錄狀態,都是無記憶遍歷,它們是一種非常低階的手段。就拿上述**:
for (int j = 0; j < capital.length; j++)
}}
遍歷整個capital陣列,只為找到最大值?更何況,當我使用完該最大值,我還得從capital陣列中把它標識為不可使用狀態,所以這樣乙個迴圈遍歷,每次都得遍歷整個陣列,然後找出乙個次大的。
嘿,這個特徵比較符合優先佇列了,用過的元素直接poll出去,而在隊頭的元素是次大的,直接把它poll出來。而構建優先佇列的時間複雜度只有o(
logn
) ,這是高階資料結構本身的特點,在構建之初就把大小關係維護進去,讓它再插入新元素時,能夠以較快的速度篩選出最大or最小。
思路:
1. 構造乙個pair對,把profit和capital關聯起來,兩者有著一一對應的關係。
2. 篩選資產,在當前總資產下,把所有capital[i]小於等於當前總資產取出,並存入另外乙個優先佇列中。
3. 該優先佇列維護profit的大小關係,隊頭永遠是符合資產中的最大profit(一種貪心策略)
總結:
看到刪除+最大or最小,想想優先佇列。**如下:
private
class
pair
}public
intfindmaximizedcapital(int k, int w, int profits, int capital)
priorityqueueq1 = new priorityqueue<>((o1,o2) -> (o1.capital - o2.capital));
priorityqueueq2 = new priorityqueue<>((o1,o2) -> (o2.profit - o1.profit));
for (int i = 0; i < n; i++)
int total = w;
for (int i = 0; i < k; i++)
if (q2.isempty()) return total;
total += q2.poll().profit;
}return total;
}
這道題可謂是麻雀雖小,五臟俱全啊,貪心的味道很濃,得深入分析分析。
首先,看到這道題的第一眼,我想到了遞迴,思路如下:
根據當前能夠jump的步數,選擇後續的位置,這樣就變成了相同的子問題,而只要最終pos能夠抵達陣列末端就能返回true。可謂是信心滿滿啊,為了防止tle,還加了記憶化手段,**如下:
public
boolean
canjump(int nums)
private
boolean
canjump(int nums, int pos,boolean dp)}}
dp[pos] = true;
return
false;
}
呵呵噠,stackoverflow了,這只能說明我還太嫩,遞迴顯然無法解決該問題了,那這樣,就用動規咯,所以又想了個動規的方案。**如下:
public
boolean
canjump(int nums)
}return dp[nums.length - 1];
}
結果tle了,tle的原因在於step,上述**,i每遞增一次,都會更新step步的dp,如:
nums = [25000,25000,24000,1]
顯然沒必要更新step步,25000能走的位置涵蓋了所有位置,應該直接返回true即可。
此時,有了貪心,該貪心的含義是說,每到乙個新的位置時,更新我能覆蓋的所有範圍(取最大),這就意味著,dp的狀態沒必要全部更新,因為我們知道在該範圍內的dp都可以是true,換句話說,我們只需要知道乙個邊界即可。
所以**如下:
public
boolean
canjump(int nums)
return
true;
}
結構比起動規簡單很多,只需要o(
n)的時間複雜度。
這道題還未理解它,它的思路嘗試來證明下,幫助理解這道題為什麼是貪心。
思路:
patching array該問題的關鍵點在於用nums原有的資料集去構造0~n的數,舉個最簡單的例子:
nums = [1,2,5] n = 7
如何構造所有的和數?我們知道它們所有的和可以用三位1來表示:
111 表示 1+2+5=8
110 表示 1+2 = 3
所以總共有8種表示方法,如下:
000,001,010,011,100,101,110,111
得到的和從小到大排列為:
0,1,2,3,5,6,7,8
此處,我們可以明顯看到當n=7時,缺了乙個4,所以我們必須得補上。所以,
nums = [1,2,4,5] n = 7
以同樣的方式構造所有和,得到:
0,1,2,3,4,5,6,7 (由1,2,4得)
在此處,我們還發現乙個規律,當打乙個補丁滿足連續的數後,
我們在構造5的所有和時,可以直接在原來構造的和上加個5,所以有:
0,1,2,3,4,5,6,7
5,6,7,8,9,10,11,12
所以n在0~12之內都能滿足條件,此時我們再來nums = [1,2,4,5,23], n = 100
我們知道:[1,2,4,5]構造的連續和為:
0,1,2,3,4,5,6,7,8,9,10,11,12
而為了能夠構造盡可能多的和,構造的補丁一定為13,因為:
0,1,...,12
| 13,14,...,25
在這裡你看到了貪心,nums = [1,2,4,5],但我們沒有構造7的原因是因為構造7所能覆蓋的範圍非常少,如下:
0,1,2,...,6,...,12
| 7,8,9,10,...,19
構造7得到的範圍為0-19,構造13能夠得到的範圍為0-25,你選誰?
在這裡明確乙個補丁的性質,在已有的連續和上,新的連續和是【補丁+已有連續和】,想想000-111,擴充套件到4位的情況。
而連續和我們只要維護乙個界即可,所以有了網上大多數的做法,對具體做法感興趣的,可以搜搜。
**如下:
public
intminpatches(int nums, int n)
else
}return added;
}
注意miss的long,防止溢位,進入死迴圈。 貪心演算法 1 演算法導論 21
我們先來看看乙個適應貪心演算法求解的問題 選擇活動問題。假定有乙個 n 個活動的集合 s 每個活動 a i 的舉辦時間為 s i,f i 0 leqslant s i f i 並且假定集合 s 中的活動都已按結束時間遞增的順序排列好。由於某些原因,這些活動在同一時刻只能有乙個被舉辦,即對於任意兩個活...
面試衝刺演算法系列 21
看到公升序,降序等代表有序的詞就可以優先考慮二分法。設定兩個指標,分別指向第乙個k和最後乙個k,最後乙個k減去第乙個k的索引即可獲得k的個數。public class solutionif firstk 1 lastk 1 return0 public intfindfirstk int array...
演算法細節系列(26) 區間
詳細 可以fork下github上leetcode專案,不定期更新。題目摘自leetcode 思路 該開始使用了for迴圈中加入了stack的結構,但發現這種思路很難逼近答案。正確的思路應該為 for 如下 public listmerge listintervals else 合併到一半的區間最後...