用于回顾数据结构与算法时刷题的一些经验记录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; } };