目录
貪心算法理論基礎
貪心算法沒有套路,最多就是常識性推倒加上舉反例。
貪心算法的本質是選擇每一階段的局部最優解,經過堆疊達到全局最優
常識性推導:
就像隨想錄所舉的例子,拿鈔票,拿十次讓最後結果的利益最大化
那每次都拿最大的面額的鈔票,到第十次後就可以拿到面額最大的數量
舉反例:
假設要將東西放在包包當中,東西分別有兩個屬性分別是大小與價值
但包包的空間是有限的,假設每次都拿最大的或價值最高的,比如說有一個高價值但大小也高的可能放下去只能放一個,但低價值的比較小累加起來,又比這單一個高價值的大物件有價值。所以在這個時候就無法使用貪心算法,因為無法透過每次拿局部最優的條件,堆疊到全局最優。
所以在貪心算法上,就會需要自己去模擬狀態,如果沒有想到反例,就可以試試看貪心算法,不然就可能需要動態規劃來解決問題。
但算法隨想錄還是有個大略性的贪心算法一般分为如下四步驟:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
並且最重要的一點,因為貪心算法的無規律性,如果在刷的過程中不知道該如何解決,直接看題解。
讀題
455.分发饼干
自己看到题目的第一想法
最直觀的想法就是將最適合的餅乾分給適合的小孩,最好是最小的胃口拿到最小剛好適合他胃口的餅乾,那做到這點第一步就是要排序,這樣就可以對應最小的人與餅乾。一開始是用兩個迴圈去跑,並且有分配到餅乾的就標記起來。
之後又優化成用雙指針的想法在一個迴圈中解決這個問題,假設最小的餅乾符合最小的胃口,最小的胃口數值才加加。否則只移動餅乾的陣列。
376. 摆动序列
自己看到题目的第一想法
最初的想法是要可以刪除部分節點達到最大的搖擺子序列,那我的想法是用count紀錄有變化的時候,如果沒有變化則不變,有變化並且符合條件就是一個為正一個為負就加加,但實行這個解法過後,發現有些特殊狀況就會發生問題,假設一開始沒變化,之後有變化就無法解決,或者從頭至尾都沒有變化,也會出現問題,不知道該如何解決
看完代码随想录之后的想法
看完之後,我發現我的作法本質上就是沒有考慮到單調平坡的狀況,所以導致我的解法出現了問題,看完卡哥的講解後,很快就解除我的疑惑了。
53. 最大子序和
自己看到题目的第一想法
原本想先用排序,但發現不能使用,但要怎麼找到最大的子序和我沒有想法。
看完代码随想录之后的想法
連續和 + nums[i]
如果連續和大於result,更新result
假設連續和為正,那繼續計數
假設連續和為負,那在下一個數為初始加加的位置,讓連續和初始為0
所以假設遇到全都是負數的狀況,那result也只會紀錄最大的負數。
455.分发饼干 - 實作
思路
- 排序s, g
- 設定雙指針
- 最小的餅乾假設大於等於最小的胃口量則,最小的胃口量才++,不然只有餅乾會持續++
- 假設s或g大於size 則跳出迴圈
- return 胃口量的point等同有幾個人被滿足
Code
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int g_point = 0;
int s_point = 0;
for(g_point = 0, s_point = 0; s_point < s.size(); s_point++) {
if(s[s_point] >= g[g_point]){
g_point++;
}
if(g_point >= g.size()) break;
}
return g_point;
}
};
376. 擺動序列 - 實作
思路
錯誤思路
- 一開始設為1,假設有變化時count++,並且更新max count值
- 最後return count
錯誤點:
- 是nums.size() < 2 return 1
- 沒有考慮到單調平坡的狀況
正確思路
- 初始化result = 1, preDiff, curDiff三個值
- 遍歷所有節點,當前後有正負數差別時,result++ 並記錄preDiff值,如果波動,preDiff值不用變動
- 最後return result
Code
錯誤代碼
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int count = 0;
int max_count = 0;
if(nums.size() <= 2) return 1;
for(int i = 0; i + 1 < nums.size(); i++) {
if(i == 0) {
count = 1;
} else {
if(nums[i] - nums[i - 1] > 0 && nums[i + 1] - nums[i] < 0) count++;
else if (nums[i] - nums[i - 1] < 0 && nums[i + 1] - nums[i] > 0) count++;
else count = 1;
}
if(count > max_count) max_count = count;
}
return max_count;
}
};
正確代碼
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int preDiff = 0;
int curDiff = 0;
int result = 1;
if(nums.size() < 2) return 1;
for(int i = 0; i + 1 < nums.size(); i++) {
curDiff = nums[i + 1] - nums[i];
if((preDiff >= 0 && curDiff < 0) || (preDiff <= 0 && curDiff > 0)) {
result++;
preDiff = curDiff;
}
}
return result;
}
};
53. 最大子序和 - 實作
思路
- 建立一個results紀錄連續和
- for迴圈遍歷所有數組
- count += nums[i] → 計算連續和
- if count > result, result = count; → 最大的連續和,在全部都是負數的狀況下就是最大的負數
- if count < 0 count = 0 —> 連續和為負數則初始為零
Code
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int result = INT_MIN;
int count = 0;
for(int i = 0; i < nums.size(); i++) {
count += nums[i];
if(count > result) result = count;
if(count < 0 ) count = 0;
}
return result;
}
};
總結
自己实现过程中遇到哪些困难
貪心算法第一天,有點被打回來了,原本回溯解得很開心,但貪心第二題跟第三題就讓我頭疼了
擺動序列的困難點是卡在單調平陂沒有去考慮到,導致無法通過,這個看卡哥的影片很快就理解了
最大子序和則是沒有考慮到暴力姊也沒有想到貪心解,但看完之後又感覺不難,貪心的算法真的很奇妙
今日收获,记录一下自己的学习时长
今天大概學了2hr,基本上都是在想怎麼歸納狀況,實際的code都不難寫,很容易就寫出來了。
相關資料
● 今日学习的文章链接和视频链接
理论基础
https://programmercarl.com/贪心算法理论基础.html
455.分发饼干
https://programmercarl.com/0455.分发饼干.html
376. 摆动序列
https://programmercarl.com/0376.摆动序列.html