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;
    }
};
相关文章
相关标签/搜索