用於回顧數據結構與算法時刷題的一些經驗記錄git
(提出對應的貪心算法時最好本身舉例子試試可否可行)算法
文章目錄
- [455. 分發餅乾](https://leetcode-cn.com/problems/assign-cookies/)
- [376. 擺動序列](https://leetcode-cn.com/problems/wiggle-subsequence/)
- [402. 移掉K位數字](https://leetcode-cn.com/problems/remove-k-digits/)
- [55. 跳躍遊戲](https://leetcode-cn.com/problems/jump-game/)
- [45. 跳躍遊戲 II](https://leetcode-cn.com/problems/jump-game-ii/)
- [134. 加油站](https://leetcode-cn.com/problems/gas-station/)
- [452. 用最少數量的箭引爆氣球](https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/)
- [135. 分發糖果](https://leetcode-cn.com/problems/candy/)
- [921. 使括號有效的最少添加](https://leetcode-cn.com/problems/minimum-add-to-make-parentheses-valid/)
- [1326. 灌溉花園的最少水龍頭數目](https://leetcode-cn.com/problems/minimum-number-of-taps-to-open-to-water-a-garden/)
455. 分發餅乾
假設你是一位很棒的家長,想要給你的孩子們一些小餅乾。可是,每一個孩子最多隻能給一塊餅乾。對每一個孩子 i i i ,都有一個胃口值 g i gi gi ,這是能讓孩子們知足胃口的餅乾的最小尺寸;而且每塊餅乾 j j j,都有一個尺寸 s j sj sj 。若是 s j > = g i sj >= gi sj>=gi,咱們能夠將這個餅乾 j j j分配給孩子 i i i,這個孩子會獲得知足。你的目標是儘量知足越多數量的孩子,並輸出這個最大數值。數組
注意:cookie
你能夠假設胃口值爲正,且一個小朋友最多隻能擁有一塊餅乾。數據結構
示例 1: 輸入: [1,2,3], [1,1] 輸出: 1 解釋: 你有三個孩子和兩塊小餅乾,3個孩子的胃口值分別是:1,2,3。 雖然你有兩塊小餅乾,因爲他們的尺寸都是1,你只能讓胃口值是1的孩子知足。因此你應該輸出1。
分析:因爲每一個孩子最多隻須要一個餅乾,而且咱們須要的是知足儘量多的孩子,所以咱們有以下策略app
- 若是一個孩子能被更小的餅乾知足,則就應該採用更小的餅乾,儘可能保留大的餅乾給胃口更大的孩子
- 若是一個餅乾不能知足胃口最小的孩子,故它將不能知足每一個孩子
所以,咱們能夠對 餅乾尺寸和孩子胃口進行排序,而後遍歷餅乾尺寸。ui
- 若是當前餅乾能夠知足當前孩子,就知足該孩子,向後遍歷餅乾和孩子胃口
- 若是當前餅乾不能夠知足當前孩子,說明該餅乾不會再被利用,向後遍歷餅乾
- 若是孩子或者餅乾遍歷完了,則返回結果便可
class Solution { public: int findContentChildren(vector<int>& g, vector<int>& s) { sort(g.begin(),g.end()); sort(s.begin(),s.end()); int cookie=0; //表示cookie遍歷到第幾個了 int child=0; while(child<g.size()&&cookie<s.size()) { if(g[child]<=s[cookie]) //該餅乾能夠知足孩子,使用便可 child++; //孩子向後遍歷 cookie++; //餅乾向後遍歷 } return child; } };
376. 擺動序列
若是連續數字之間的差嚴格地在正數和負數之間交替,則數字序列稱爲擺動序列。第一個差(若是存在的話)多是正數或負數。少於兩個元素的序列也是擺動序列。spa
例如, [1,7,4,9,2,5] 是一個擺動序列,由於差值 (6,-3,5,-7,3) 是正負交替出現的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是擺動序列,第一個序列是由於它的前兩個差值都是正數,第二個序列是由於它的最後一個差值爲零。code
給定一個整數序列,返回做爲擺動序列的最長子序列的長度。 經過從原始序列中刪除一些(也能夠不刪除)元素來得到子序列,剩下的元素保持其原始順序。blog
分析:
可視化數字發現貪心規律
分析題目可知,實際上搖擺序列就是畫到座標軸上連線後,上下波動的線條的每一個頂點。
所以咱們能夠將相鄰數字之間差計算出來,以次表示兩個數字間是上升仍是降低關係。 須要注意的是,咱們貪心思想表如今,若是出現連續的上升或者降低,則應當取最後一個(即上升或降低最後的端點)做爲子序列節點。
- 將相鄰數字差表示出來
- 用一個變量表示當前是處於上升仍是降低,發生變化才+1
- 須要考慮頭部是平緩的狀況,將其度過
class Solution { public: int wiggleMaxLength(vector<int>& nums) { int length=nums.size(); int result=0; if(length<=1) return length; for(int i=0;i<length-1;i++) //將差值存入到nums中,共length-1個 { nums[i]=nums[i+1]-nums[i]; } int up_or_down=0; //0表示當前平,1表示當前爲up,2表示當前爲down int i=0; for(i=0;i<length-1;i++) //將頭部的平緩區度過,並初始化up_or_down { if(nums[i]>0) { up_or_down=1; result++; break; } else if(nums[i]<0) { up_or_down=2; result++; break; } } for(i;i<length-1;i++) { if(nums[i]>0&&up_or_down==2) //若是當前爲降低而且該值爲上升,則result+1 { up_or_down=1; result++; continue; } else if(nums[i]<0&&up_or_down==1)//若是當前爲上升而且該值爲降低,則result+1 { up_or_down=2; result++; continue; } } return result+1; } };
402. 移掉K位數字
給定一個以字符串表示的非負整數 num,移除這個數中的 k 位數字,使得剩下的數字最小。
注意: num 的長度小於 10002 且 ≥ k。 num 不會包含任何前導零。
示例 1 : 輸入: num = "1432219", k = 3 輸出: "1219" 解釋: 移除掉三個數字 4, 3, 和 2 造成一個新的最小的數字 1219。
分析:若是給定 「1432219」 ,去掉一個數字令其最大,如何去?去掉後確定減小一位,所以應該儘量地使高位數最小,分析該數字,因爲1<4,故不該該去掉1,不然高位數將增大,4>3,故去掉4,會使得第二位變爲3,從而達到儘量小。
所以去掉數字的原則:從高位向地位遍歷,若是對應的數字大於下一位數字,則把該位數字去掉,獲得的數字最小
能夠用棧存儲結果,這樣從高位向地位遍歷時若是有錯位則將其不加入棧,最終棧中存儲內容應該是每個數字不大於下一位,若是還須要刪除,那麼咱們就將棧頂pop出,直至中止。
還須要考慮的是,若是數字中有0出現如何處理(能夠考慮將其不放入棧),還要注意的是如何將棧中內容返回所需字符串。
class Solution { public: string removeKdigits(string num, int k) { vector<int> s; //用vector來表達棧便可,方便遍歷元素 string result=""; for(int i=0;i<num.length();i++) //循環遍歷 { int number=num[i]-'0'; while(s.size()!=0&&s[s.size()-1]>number&&k>0) //若是當前遍歷的數字比前面的數字小,則將前面的數字pop { s.pop_back(); k--; } if(number!=0||s.size()!=0) //0就看成沒有,不加入便可 { s.push_back(number); } } while(s.size()!=0&&k>0) //若是已經遍歷完可是還須要刪,從尾部刪便可 { s.pop_back(); k--; } for(int i=0;i<s.size();i++) //將結果轉換爲字符串 result.append(1,'0'+s[i]); if(result=="") result="0"; return result; } };
55. 跳躍遊戲
給定一個非負整數數組,你最初位於數組的第一個位置。
數組中的每一個元素表明你在該位置能夠跳躍的最大長度。
判斷你是否可以到達最後一個位置。
示例 1: 輸入: [2,3,1,1,4] 輸出: true 解釋: 咱們能夠先跳 1 步,從位置 0 到達 位置 1, 而後再從位置 1 跳 3 步到達最後一個位置。
分析:在第 i i i 個位置,最遠能夠跳到第 i + n u m [ i ] i+num[i] i+num[i] 個位置,這意味着從 i i i 到 i + n u m [ i ] i+num[i] i+num[i] 之間的位置均可以到達。所以,咱們能夠用一個 m a x _ j u m p max\_jump max_jump 存取目前能到達的最遠位置,以次遍歷所能到達的位置,若是 m a x _ j u m p max\_jump max_jump 小於目標地址,則說明不能到達。每次到達一個地方,都再次計算該位置所能到達的最遠位置,刷新 m a x _ j u m p max\_jump max_jump 。
class Solution { public: bool canJump(vector<int>& nums) { int length=nums.size(); int max_jump=0; for(int i=0;i<length-1;i++) // { if(max_jump<i) //說明不能再向前 return false; if(nums[i]+i>max_jump) //說明能夠達到更遠,刷新max_jump max_jump=nums[i]+i; } if(max_jump>=length-1) //說明能夠跳到目標位置 return true; return false; } };
45. 跳躍遊戲 II
給定一個非負整數數組,你最初位於數組的第一個位置。數組中的每一個元素表明你在該位置能夠跳躍的最大長度。
你的目標是使用最少的跳躍次數到達數組的最後一個位置。
示例: 輸入: [2,3,1,1,4] 輸出: 2 解釋: 跳到最後一個位置的最小跳躍數是 2。 從下標爲 0 跳到下標爲 1 的位置,跳 1 步,而後跳 3 步到達數組的最後一個位置。
分析:
- 若是如今在某一個起跳點,且該起跳點的距離爲 d ,則以後的 $d $ 個點是能夠從目前位置一步跳到的。即若是當前是第一次跳躍,則對於後面d個點來講,都是第二次跳躍,故後面的d個點的跳躍最大距離,就是目前來講第二次跳躍的最遠距離
- 所以在實現時,用 m a x _ j u m p max\_jump max_jump 標記最遠距離,用 e n d end end 表示當前步數所能到達的最遠距離,所以每次到達 e n d end end處,就須要更新 t i m e s times times 和 e n d end end ,而 m a x _ j u m p max\_jump max_jump是在每個位置都要更新的。基於該思路,從第一個位置開始進行貪心,首先令 e n d end end 爲當前位置能到達的最遠,則說明從第一個位置到 e n d end end 都是一步可達的,而後遍歷這些位置,再次計算這些位置所能到達的最遠處的最大值,做爲新的 e n d end end .
class Solution { public: int jump(vector<int>& nums) { int length=nums.size(); int times=0; int max_jump=0; int end=0; for(int i=0;i<length-1;i++) { max_jump=max(max_jump,nums[i]+i); //刷新max_jump if(i==end) //到達times步所能到達的最遠距離了,以後須要times+1步 { times++; end=max_jump; //end更新爲下一步能到達的最遠距離 } } return times; } };
134. 加油站
在一條環路上有 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 可爲起始索引。
分析:將gas和cost聯合起來考慮,當前的問題能夠簡化爲 從某一點出發,在其餘地方會有一個油量,該油量可正可負,實際上就是到該地方得到的 gas 減去到達該地方所需的 cost 。所以,該題就相似於最大連續數列和了 。
- 該題特殊在是環行路,且若是有解則解惟一
- 所以設置一個sum變量,sum爲 ∑ g a s i − c o s t i \sum{gas_i}-cost_i ∑gasi−costi ,最終若是sum>=0,則說明存在解,不然無解
- 從第一個節點開始,設置其爲起始點,若是從起始點到某個點的和是正數,則繼續遍歷,若是的到達某一點爲負數,說明從該點出發不能遍歷(不但這個起始點出發不行,並且說明了從這個起始點到該點間的全部點都不能夠做爲起始點)。所以將起始點設置爲當前點的下一個節點(由於當前節點必定是耗油而不是加油),重置space,繼續遍歷
class Solution { public: int canCompleteCircuit(vector<int>& gas, vector<int>& cost) { int start=0;// 從start出發 int spare=0; //從start出發的話到當前位置的油量 int sum=0; //記錄總和 for(int i=0;i<gas.size();i++) { spare+=gas[i]-cost[i]; // sum+=gas[i]-cost[i]; if(spare<0) //spare<0說明從start開始不知足,將start更新爲當前位置的下一個位置 { start=i+1; spare=0; } } return (sum<0)?-1:(start); } };
452. 用最少數量的箭引爆氣球
在二維空間中有許多球形的氣球。對於每一個氣球,提供的輸入是水平方向上,氣球直徑的開始和結束座標。因爲它是水平的,因此y座標並不重要,所以只要知道開始和結束的x座標就足夠了。開始座標老是小於結束座標。平面內最多存在104個氣球。
一支弓箭能夠沿着x軸從不一樣點徹底垂直地射出。在座標x處射出一支箭,如有一個氣球的直徑的開始和結束座標爲 x s t a r t x_{start} xstart, x e n d x_{end} xend, 且知足 x s t a r t x_{start} xstart ≤ x ≤ x e n d x_{end} xend,則該氣球會被引爆。能夠射出的弓箭的數量沒有限制。 弓箭一旦被射出以後,能夠無限地前進。咱們想找到使得全部氣球所有被引爆,所需的弓箭的最小數量.
Example: 輸入: [[10,16], [2,8], [1,6], [7,12]] 輸出: 2 解釋: 對於該樣例,咱們能夠在x = 6(射爆[2,8],[1,6]兩個氣球)和 x = 11(射爆另外兩個氣球)。
分析:將氣球按照開始座標進行排序,而後維護一個射擊區間,以次考慮氣球,若是能夠經過調整射擊區間使該氣球可以被一塊兒引爆,則調整射擊區間便可。若是一個氣球在射擊區間以外,則說明須要增長弓箭,再次射擊。
bool cmp(vector<int> &a,vector<int> &b) //按照begin排序 { return a[0]<b[0]; } class Solution { public: int findMinArrowShots(vector< vector<int> >& points) { if(points.size()<=1) return points.size(); sort(points.begin(),points.end(),cmp); //按照左端點從小到大排序 int result=1; //弓箭數量 int shoot_left=points[0][0]; //維護一個射擊區間 int shoot_right=points[0][1]; for(int i=1;i<points.size();i++) { if(points[i][0]<=shoot_right) //說明能夠一併射擊 { shoot_left=points[i][0]; //射擊區間左端點向右移動 if(points[i][1]<shoot_right) shoot_right=points[i][1]; } else //不能夠一併射擊, { result++; shoot_left=points[i][0]; shoot_right=points[i][1]; } } return result; } };
135. 分發糖果
老師想給孩子們分發糖果,有 N 個孩子站成了一條直線,老師會根據每一個孩子的表現,預先給他們評分。
你須要按照如下要求,幫助老師給這些孩子分發糖果:
每一個孩子至少分配到 1 個糖果。 相鄰的孩子中,評分高的孩子必須得到更多的糖果。
那麼這樣下來,老師至少須要準備多少顆糖果呢?
示例 1: 輸入: [1,0,2] 輸出: 5 解釋: 你能夠分別給這三個孩子分發 2、1、2 顆糖果。
分析:相鄰的孩子中,評分高的孩子必須得到更多的糖果,所以假設A與B相鄰(A在B左側)
- 若是 r a t i n g s A < r a t i n g s B ratings_A <ratings_B ratingsA<ratingsB ,則B應當比A糖果多,左規則
- 若是 r a t i n g s A > r a t i n g s B ratings_A >ratings_B ratingsA>ratingsB , 則A應當比B糖果多,右規則
所以,令每個相鄰的孩子都知足以上兩個規則便可。
- 設置兩個變量數組,left和right,分別用來存儲單獨知足左規則和右規則所需的最小糖數
- 從左向右遍歷,若是 r a t i n g s B > r a t i n g s A ratings_B>ratings_A ratingsB>ratingsA ,則令 l e f t [ B ] = l e f t [ A ] + 1 left[B]=left[A]+1 left[B]=left[A]+1 ,不然保持不變便可
- 從右向左遍歷,若是 r a t i n g s A > r a t i n g s B ratings_A >ratings_B ratingsA>ratingsB ,則令 $right[A]=right[B]+1 $,不然保持不變便可
- 所以對於任意一個學生, m a x ( l e f t [ i ] , r i g h t [ i ] ) max(left[i],right[i]) max(left[i],right[i]) 一定知足要求
- 所以對 m a x ( l e f t [ i ] , r i g h t [ i ] ) max(left[i],right[i]) max(left[i],right[i]) 求和便可
class Solution { public: int candy(vector<int>& ratings) { int length=ratings.size(); if(length<=1) return length; int left[length]; int right[length]; for(int i=0;i<length;i++) { left[i]=right[i]=1; } for(int i=1;i<length;i++) //從左向右遍歷 { if(ratings[i]>ratings[i-1]) left[i]=left[i-1]+1; } for(int i=length-2;i>=0;i--) { if(ratings[i]>ratings[i+1]) right[i]=right[i+1]+1; } int sum=0; for(int i=0;i<length;i++) { sum+=max(right[i],left[i]); } return sum; } };
921. 使括號有效的最少添加
給定一個由 ′ ( ′ '(' ′(′和 ′ ) ′ ')' ′)′括號組成的字符串 S,咱們須要添加最少的括號( ′ ( ′ '(' ′(′ 或是 ′ ) ′ ')' ′)′,能夠在任何位置),以使獲得的括號字符串有效。
從形式上講,只有知足下面幾點之一,括號字符串纔是有效的:
它是一個空字符串,或者它能夠被寫成 AB (A 與 B 鏈接), 其中 A 和 B 都是有效字符串,或者它能夠被寫做 (A),其中 A 是有效字符串。
給定一個括號字符串,返回爲使結果字符串有效而必須添加的最少括號數。
分析:由於字符串中只存在左括號或者右括號,所以將能成立的完整括號消除,則最後剩下的就是須要添加括號進行消除的。 在括號匹配中,經常使用棧做爲數據結構,若是添加的和棧頂的匹配可消除,則將棧頂彈出,不然將其入棧。
class Solution { public: int minAddToMakeValid(string S) { stack<char> sta; for(int i=0;i<S.size();i++) { if(!sta.empty()&&sta.top()=='('&&S[i]==')') //可消除 sta.pop(); else sta.push(S[i]); //不可消除則push進 } return sta.size(); } };
1326. 灌溉花園的最少水龍頭數目
在 x 軸上有一個一維的花園。花園長度爲 n n n,從點 0 開始,到點 n n n 結束。
花園裏總共有 n + 1 n + 1 n+1 個水龍頭,分別位於 [0, 1, …, n] 。
給你一個整數 n n n 和一個長度爲 n + 1 n + 1 n+1 的整數數組 r a n g e s ranges ranges ,其中 r a n g e s [ i ] ranges[i] ranges[i] (下標從 0 開始)表示:若是打開點 i 處的水龍頭,能夠灌溉的區域爲 [ i − r a n g e s [ i ] , i + r a n g e s [ i ] ] [i - ranges[i], i + ranges[i]] [i−ranges[i],i+ranges[i]] 。
請你返回能夠灌溉整個花園的 最少水龍頭數目 。若是花園始終存在沒法灌溉到的地方,請你返回 -1 。
分析:感受該題目有點 45. 跳躍遊戲 II 和452. 用最少數量的箭引爆氣球 的結合版
所以大體思路爲:首先要肯定出每個水龍頭的灌溉區間,而後對其左排序,貪心思想爲從一個起點出發,應該儘量地選取右屆最大的區間,即至關於肯定出第一個水龍頭灌溉的區間後,在該區間內找到第二個水龍頭,儘量使得該水龍頭的右屆最遠,依次繼續貪心。
class Solution { public: //題解:貪心法 //1:首先遍歷rangs,創建跳躍遊戲Ⅱ中的跳躍數組,left表示起始點,right-left表示最大跳躍距離 //2:使用跳躍遊戲Ⅱ中的代碼便可,不過每次到達邊界end,需判斷furthest是否超過end int minTaps(int n, vector<int>& ranges) { //一、創建跳躍數組 vector<int> jumps(n+1); for(int i=0;i<n+1;++i){ int left=max(i-ranges[i],0); int right=min(i+ranges[i],n); if(jumps[left]<right-left){ jumps[left]=right-left; } } //二、貪心法跳躍 int furthest=0,end=0,count=0; for(int i=0;i<n;++i){//注意最後一個點不能遍歷,由於在i==end==0時,count多統計了一次 furthest=max(jumps[i]+i,furthest); if(furthest>=n){ count++; break; } if(i==end){ //若最遠距離沒有超過邊界,直接返回-1 if(furthest<=end)return -1; count++; end = furthest; } } return count; } };