用于回顾数据结构与算法时刷题的一些经验记录
(提出对应的贪心算法时最好自己举例子试试能否可行)
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 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,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
注意:
你可以假设胃口值为正,且一个小朋友最多只能拥有一块饼干。
示例 1:
输入: [1,2,3], [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。所以你应该输出1。
分析:由于每个孩子最多只需要一个饼干,并且我们需要的是满足尽可能多的孩子,因此我们有如下策略
因此,我们可以对 饼干尺寸和孩子胃口进行排序,然后遍历饼干尺寸。
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;
}
};
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
分析:
可视化数字发现贪心规律
分析题目可知,实际上摇摆序列就是画到坐标轴上连线后,上下波动的线条的每个顶点。
因此我们可以将相邻数字之间差计算出来,以次表示两个数字间是上升还是下降关系。 需要注意的是,我们贪心思想表现在,如果出现连续的上升或者下降,则应当取最后一个(即上升或下降最后的端点)作为子序列节点。
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;
}
};
给定一个以字符串表示的非负整数 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;
}
};
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 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;
}
};
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
分析:
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;
}
};
在一条环路上有 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 。因此,该题就类似于最大连续数列和了 。
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);
}
};
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以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;
}
};
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。 相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
示例 1:
输入: [1,0,2]
输出: 5
解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
分析:相邻的孩子中,评分高的孩子必须获得更多的糖果,因此假设A与B相邻(A在B左侧)
因此,令每一个相邻的孩子都满足以上两个规则即可。
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;
}
};
给定一个由 ′ ( ′ '(' ′(′和 ′ ) ′ ')' ′)′括号组成的字符串 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();
}
};
在 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) {
//1、建立跳跃数组
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;
}
}
//2、贪心法跳跃
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;
}
};