LeetCode之用C++写贪心算法
Leetcode #455 分发饼干
题名:分发饼干
描述:
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
说明:
你可以假设胃口值为正。
一个小朋友最多只能拥有一块饼干。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/assign-cookies/
方法:贪心
贪心规律
1.某个饼干如果不能满足某个孩子,则该饼干也一定不能满足需求因子更大的孩子
2.某个孩子可以用更小的饼干满足,则没必要用更大饼干满足,因为可以保留更大的饼干
3.孩子的需求因子更小则其更容易被满足,故优先从需求因子小的孩子尝试
4.用某个饼干,满足一个较大需求因子的孩子,或满足一个较小需求因子的孩子,效果是一样的(最终满足的总量不变)
代码如下:
int findContentChildren(vector<int>& g, vector<int>& s) {
int ans = 0;
//对孩子的胃口和饼干大小按升序排序,然后依据贪心规律进行模拟
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int p = 0, q = 0;
while(q < s.size()){
if(p >= g.size()) break;//p >= g.size() 表示孩子都被满足了但饼干还有的多,此时要退出循环了
if(s[q] >= g[p]){
ans++;
p++;
}
q++;
}
return ans;
}
Leetcode #435 无重叠区间
题名:无重叠区间
描述:
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
说明:
可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/non-overlapping-intervals/
方法:贪心
正确的思路其实很简单,可以分为以下三步:
1.在区间集合 intervals
中选择一个区间 x
,这个 x
是在当前所有区间中结束最早的(区间右边界最小),因此可以对 intervals
按照单个区间的右边界从小到大排序,例如 [ [1,2], [2,3], [3,4], [1,3] ]
,经过排序后成为 [ [1,2], [2,3], [1,3], [3,4] ]
。
2.把所有与 x
区间相交的区间视为有重叠的区间,那么怎么判断相交呢?可以通过区间的左边界,如果后一个区间的左边界值小于前一个区间的右边界值,则视为相交;用
noOverlapCount
用于记录不重叠区间的数量 。
3.重复步骤 1 和 2,直到将 intervals
遍历完为止。然后用用 intervals
中的区间总数减去 noOverlapCount
即是答案;
代码如下:
static bool cmp(vector<int> a, vector<int> b){
return a[1] < b[1];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if(!intervals.size()) return 0;//如果intervals为空啥都不干,返回0即可
sort(intervals.begin(), intervals.end(), cmp);//步骤1
int noOverlapCount = 1, index = 0, p = 1;
while(index + p < intervals.size()){
if(intervals[index + p][0] >= intervals[index][1]){//步骤2
noOverlapCount++;
index += p;
p = 1;
}
else p++;
}
return intervals.size() - noOverlapCount;//步骤3
}
Leetcode #452 用最少数量的箭引爆气球
题名:用最少数量的箭引爆气球
描述:
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。
一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 [xstart,xend]
, 且满足 xstart ≤ x ≤ xend
,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
Example
输入:
[[10,16], [2,8], [1,6], [7,12]]
输出:
2
解释:
对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/
方法:贪心
这一题与上面一题 Leetcode #435 无重叠区间
类似,直接上代码:
static bool cmp(vector<int> a, vector<int> b){
return a[1] < b[1];
}
int findMinArrowShots(vector<vector<int>>& points) {
if(!points.size()) return 0;
int ans = 1, p = 1, anchor = 0;//anchor 用于锚定,p用于移动
sort(points.begin(), points.end(), cmp);
while(anchor + p < points.size()){
if(points[anchor + p][0] <= points[anchor][1]){//将多个重叠的气球视为一个气球,只需要一支箭
p++;
}
else{
ans++;
anchor += p;
p = 1;
}
}
return ans;
}
Leetcode #406 根据身高重建队列
题名:根据身高重建队列
描述:
假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对 (h, k)
表示,其中 h 是这个人的身高, k 是排在这个人前面且身高大于或等于 h 的人数。 编写一个算法来重建这个队列。
注意:
总人数少于1100人。
Example
输入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
输出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/queue-reconstruction-by-height/
方法:贪心
对于身高比较高的人来说,比他矮的人他是‘看不见的’, 所以我们可以先对同一身高的人群按k值从小到大排序;再按身高从大到小按k值插入ans中。
代码如下:
static bool cmp(vector<int> a, vector<int> b){//排序规则
if(a[0] != b[0]) return a[0] > b[0];
return a[1] < b[1];
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort(people.begin(), people.end(), cmp);
vector<vector<int> > ans;
int index = 0;
while(index < people.size()){
ans.insert(ans.begin() + people[index][1], people[index]);
index++;
}
return ans;
}
Leetcode #121 买卖股票的最佳时机
题名:买卖股票的最佳时机
描述:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
Example1
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
Example2
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
方法:贪心
我总是期望在股票价格最低的时候入手,在最高的时候抛出。
int maxProfit(vector<int>& prices) {
if(!prices.size()) return 0;
int shareVal = prices[0], maxP = 0;
for(int i = 0;i < prices.size();i++){
if(shareVal > prices[i]){//如果有价值更低的股票,我就入手
shareVal = prices[i];
}
else if(shareVal < prices[i]){//如果有股票价格变高了我就抛出
int tmp = prices[i] - shareVal;
if(tmp > maxP) maxP = tmp;//记录下最大利益
}
}
return maxP;
}
Leetcode #122 买卖股票的最佳时机 II
题名:买卖股票的最佳时机 II
描述:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
Example1
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
Example2
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
方法:贪心
股票买卖策略:
1.单独交易日:设今天价格p1, 明天价格p2,则今天买入,明天卖出可赚取金额 p2 - p1
(负值代表亏损)。
2.连续上涨交易日:设此上涨交易日股票价格分别为 'p1, p2, p3,..., pn',则第一天买最后一天麦受益最大。即 `pn - p1 = (p2 - p1) + (p3 - p2) +...+(pn - pn-1)。
3.连续下降交易日:则不买卖收益最大,即不会亏钱。
代码如下:
int maxProfit(vector<int>& prices) {
int res = 0;
for(int i = 1;i < prices.size();i++){
int profit = prices[i] - prices[i - 1];
if(profit > 0) res += profit;
}
return res;
}
Leetcode #392 判断子序列
题名:判断子序列
描述:
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
Example1
s = "abc", t = "ahbgdc"
返回 true.
Example2
s = "axc", t = "ahbgdc"
返回 false.
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/is-subsequence/
方法:从头到尾遍历
双指针,用sp指向段字符串,tp指向长字符串。tp不断向后移,匹配到相等的sp向后移,因为不是严格意义上的子串,只要顺序一致就可以,所以sp不需要回溯。用sp是否超出s字符串长度来判断是否找到子串。代码如下:
bool isSubsequence(string s, string t) {
string::iterator sp ,tp;
sp = s.begin(); tp = t.begin();
while(sp != s.end() && tp != t.end()){
if(*sp == *tp){
sp++;
}
tp++;
}
return sp == s.end();
}
Leetcode #665 非递减数列
题名:非递减数列
描述:
给你一个长度为 n 的整数数组,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。
我们是这样定义一个非递减数列的: 对于数组中所有的 i (1 <= i < n),总满足 array[i] <= array[i + 1]。
方法:贪心
这种问题首先需要对特殊数据进行分析,从特殊到一般,总结出规律来,在进行编程!
数据一: 4, 2, 3
1.对于这串数据,当比较到4, 2
时,出现了4 > 2
的情况,那么是把4改成2还是把2改成4呢?我们人类很好判断出只要把4改成<=2的数即可;因为后面出现了3,把2改成4的话,后面的数就不符合了!这里就总结出了一条规律:> 当nums[i - 1] > nums[i]
时,并且i - 1
是第一个数时,令nums[i - 1] = nums[i]
;
2.并且总结处到出现nums[i - 1] > nums[i]
的情况时,需要考虑到底是令nums[i - 1] = nums[i]
,还是nums[i] = nums[i - 1]
;
数据二: 3, 4, 2, 4
1.当我们比较到4, 2
时,出现了nums[i - 1] > nums[i]
的情况,如果令nums[i - 1] = nums[i]
,就变成了3, 2, 2, 4
明显不对;如果令nums[i] = nums[i - 1]
,就变成了3, 4, 4, 4
就对了;从这里我们发现nums[i]
不仅要与 nums[i - 1]
比较还要跟nums[i - 2]
比较;
从以上的归纳得出的结论:
1.出现nums[i - 1] > nums[i]
需要将nums[i]
与nums[i - 1]
和nums[i - 2]
进行比较;不然的话直接令i++;
2.当nums[i - 2] > nums[i - 1]
时,令nums[i] = nums[i - 1]
;反之令令nums[i - 1] = nums[i]
代码如下:
bool checkPossibility(vector<int>& nums) {
int count = 1, end = nums.size();
for(int i= 1;i < end;i++){
if(nums[i - 1] <= nums[i]) continue;
count--;
if(i - 2 >= 0 && nums[i - 2] > nums[i])
nums[i] = nums[i - 1];
else nums[i - 1] = nums[i];
if(count < 0) return false;
}
return true;
}
Leetcode #763 划分字母区间
题名:划分字母区间
描述:
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。
Example1
输入: S = "ababcbacadefegdehijhklij"
输出: [9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。
具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/partition-labels/
方法:贪心 + STL库的利用
要想划分的片段最多,那么就是利用贪心的思想,当划分出的字符串s1, s1里面的字符不再次在S中出现时,立马就将其从整个S中划分出来。
利用STL中的set和string容器。
代码如下:
vector<int> partitionLabels(string S) {
vector<int> ans;
set<char> st;//set是集合,在这个容器里面没有重复的元素
string str;//利用好string容器的find方法;
int index = 0;
while(index < S.size()){
bool flag = true;
str += S[index];
st.insert(S[index]);
for(auto a : st){
if(index + 1 < S.size() && S.find(a, index + 1) != string::npos){ //size_t string::find(const char c, size_t pos) const noexcept;
flag = false;
break;
}
}
if(flag){
ans.push_back(str.size());
str.clear();
}
index++;
}
return ans;
}