leetcode 經典貪心算法題目(思路、方法、code)

用於回顧數據結構與算法時刷題的一些經驗記錄git

(提出對應的貪心算法時最好本身舉例子試試可否可行)算法

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, 而後再從位置 13 步到達最後一個位置。

分析:在第 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 gasicosti ,最終若是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
解釋: 你能夠分別給這三個孩子分發 212 顆糖果。

分析:相鄰的孩子中,評分高的孩子必須得到更多的糖果,所以假設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]] [iranges[i],i+ranges[i]]

請你返回能夠灌溉整個花園的 最少水龍頭數目 。若是花園始終存在沒法灌溉到的地方,請你返回 -1 。
在這裏插入圖片描述

分析:感受該題目有點 45. 跳躍遊戲 II452. 用最少數量的箭引爆氣球 的結合版

所以大體思路爲:首先要肯定出每個水龍頭的灌溉區間,而後對其左排序,貪心思想爲從一個起點出發,應該儘量地選取右屆最大的區間,即至關於肯定出第一個水龍頭灌溉的區間後,在該區間內找到第二個水龍頭,儘量使得該水龍頭的右屆最遠,依次繼續貪心

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;
    }
};
相關文章
相關標籤/搜索