本文的题目选取以及部分思路来自于高畅的《Leetcode101》与力扣题解中的用户思路与解法,仅供自己学习记录~
贪心算法的思想是保证每次操作都是局部最优,从而使最后的结果是全局最优的。
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子i
,都有一个胃口值g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干j
,都有一个尺寸s[j]
。如果s[j] >= g[i]
,我们可以将这个饼干j
分配给孩子i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例1:
输入: g = [1,2,3], s = [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
示例2:
输入: g = [1,2], s = [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2。
思路:
因为饥饿度最小的孩子最容易吃饱,所以我们先考虑这个孩子,给剩余孩子里最小饥饿度的孩子分配最小的能饱腹的饼干。
代码:
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
//分发饼干
sort(g.begin(),g.end());//对小孩胃口值进行从小到大的排序
sort(s.begin(),s.end());//对饼干尺寸值进行从小到大的排序
int i = 0, j = 0;//分别指向小孩胃口值和饼干的尺寸
while(i < g.size() && j < s.size())//限定不超出范围
{
if(g[i] <= s[j]) ++i;//如果小孩胃口值小于或者等于饼干尺寸,则转到下一个小孩,否则仍停留在这个小孩
++j;//无论饼干尺寸是否能满足当前小孩,都需要指向下一个饼干进行判断(饼干尺寸无法满足当前小孩时,证明后面胃口更大的小孩更加无法满足,这个饼干无法被任何小孩吃)
}
return i;//得到最多可满足的小孩数量
}
};
n
个孩子站成一排。给你一个整数数组ratings
表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
每个孩子至少分配到 1
个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。
示例1:
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。
示例2:
输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
思路:
两次遍历:首先把所有孩子的糖果数初始化为 1;
代码:
class Solution {
public:
int candy(vector<int>& ratings) {
int n = ratings.size();
if (n < 2) return n;//特殊情况
vector<int> c(n,1);//长度为n的容器,初始化均为1
for (int i = 0; i < n - 1;i++)//从左往右遍历
{
if (ratings[i] < ratings[i+1]) c[i+1] = c[i] + 1;
}
for (int j = n - 1;j > 0;j--)
{
if (ratings[j-1] > ratings[j] && c[j-1] <= c[j]) c[j-1] = c[j] + 1;
}
return accumulate(c.begin(),c.end(),0);//accumulate函数包含在#include 头文件下,其中有三个参数,前两个参数是累加元素的范围,第三个参数是累加的初值。
}
};
给定一个区间的集合intervals
,其中intervals[i] = [starti, endi]
。返回需要移除区间的最小数量,使剩余区间互不重叠 。
示例1:
输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。这里是引用
示例2:
输入: intervals = [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3:
输入: intervals = [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
思路:
求最少的移除区间个数,等价于尽量多保留不重叠的区间。在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。因此,我们采取的贪心策略为,优先保留结尾小且不相交的区间。具体实现方法为,先把区间按照结尾的大小进行增序排序,每次选择结尾最小且和前一个选择的区间不重叠的区间。
代码:
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if(intervals.empty())
{
return 0;
}//特殊情况
int n = intervals.size();
int cnt = 0;
sort(intervals.begin(),intervals.end(),[](vector<int>& a,vector<int>& b)
{
return a[1] < b[1];
});//将数组中每个区间的结尾进行递增排序
int prev = intervals[0][1];
for (int i = 1;i < n;i++)
{
if(intervals[i][0] < prev)//如果下一个区间的开头小于上一个区间的结尾,证明两区间重叠,需要移除该区间
{
++cnt;
}
else{//否则将当前区间的结尾赋值给prev
prev = intervals[i][1];
}
}
return cnt;
}
};
假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给你一个整数数组 flowerbed
表示花坛,由若干0
和1
组成,其中0
表示没种植花,1
表示种植了花。另有一个数n
,能否在不打破种植规则的情况下种入n
朵花?能则返回true
,不能则返回false
。
示例 1:
输入:flowerbed = [1,0,0,0,1], n = 1
输出:true
示例 2:
输入:flowerbed = [1,0,0,0,1], n = 2
输出:false
思路:
从左往右遍历,能种花的条件是当前位置是0 && 前一位置是0或者边界 && 后一位置是0或者边界。
代码:
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
int size = flowerbed.size();
for (int i = 0;i < size;i++)
{
if(flowerbed[i] == 0 && (i == 0 || flowerbed[i-1] == 0) && (i == size-1 || flowerbed[i+1] == 0))
{
n--;
flowerbed[i] = 1;
}
}
return n <= 0;
}
};
有一些球形气球贴在一堵用XY
平面表示的墙面上。墙面上的气球记录在整数数组 points
,其中points[i] = [xstart, xend]
表示水平直径在 xstart
和 xend
之间的气球。你不知道气球的确切 y
坐标。
一支弓箭可以沿着 x
轴从不同点完全垂直地射出。在坐标x
处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend
, 且满足 xstart ≤ x ≤ xend
,则该气球会被引爆 。可以射出的弓箭的数量没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组points
,返回引爆所有气球所必须射出的最小弓箭数 。
示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
在x = 6处射出箭,击破气球[2,8]和[1,6]。
在x = 11处发射箭,击破气球[10,16]和[7,12]。
示例 2:
输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要4支箭。
示例 3:
输入:points = [[1,2],[2,3],[3,4],[4,5]]
输出:2
解释:气球可以用2支箭来爆破:
在x = 2处发射箭,击破气球[1,2]和[2,3]。
在x = 4处射出箭,击破气球[3,4]和[4,5]。
思路:
与435类似。
代码:
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points) {
if(points.empty()) return 0;
int n = points.size();
sort(points.begin(),points.end(),[](vector<int>& a,vector<int>& b)
{
return a[1] < b[1];
});//按照气球的结束坐标排序
int res = 1;
int pre = points[0][1];
for (int i = 1;i < n;i++)
{
if (points[i][0] > pre)不相交的条件:后一个气球的开始坐标大于(不能等于)前一个气球的结束坐标
{
res++;
pre = points[i][1];
}
}
return res;
}
};
在排序时需要注意的是:
sort(points.begin(),points.end(),[](vector<int> a,vector<int> b){
return a[1] < b[1];//传值,开销太大
sort(points.begin(),points.end(),[](const vector<int> &a,const vector<int> &b){
return a[1] < b[1];//传引用
sort(points.begin(),points.end(),[](vector<int> &a,vector<int> &b){
return a[1] < b[1];//传引用
字符串S
由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
示例:
输入:S = “ababcbacadefegdehijhklij”
输出:[9,7,8]
解释:
划分结果为 “ababcbaca”, “defegde”, “hijhklij”。
每个字母最多出现在一个片段中。
像 “ababcbacadefegde”, “hijhklij” 的划分是错误的,因为划分的片段数较少。
思路:
参考代码随想录的解题思路,贪心思路为:
代码:
class Solution {
public:
vector<int> partitionLabels(string s) {
//统计每一个字符最后出现的位置
int hash[27] = {0};
for (int i = 0;i < s.size();i++){
hash[s[i] - 'a'] = i;
}
vector<int> result;
//从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等,则找到了分割点
int left = 0;
int right = 0;
for(int i = 0;i < s.size();i++){
right = max(right,hash[s[i] - 'a']);
if(i == right){
result.push_back(right - left + 1);
left = i + 1;
}
}
return result;
}
};
给定一个数组prices
,它的第i
个元素prices[i]
表示一支给定股票第i
天的价格。
你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回0
。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
思路:
保持在最便宜的时候买入,在最贵的时候卖出,即可获得最大的利润。
代码:
提供一种会超时的暴力解法:
class Solution {
public:
int maxProfit(vector<int>& prices) {
//两两遍历的方法
if (prices.size() == 1) return 0;
int Max = 0;
for (int i = 0;i < prices.size() - 1;i++)
{
int temp = prices[i];
for (int j = i + 1;j < prices.size();j++)
{
if (prices[j] < temp) continue;
Max = max(prices[j] - temp,Max);
}
}
return Max;
}
};
贪心算法:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int Max = 0;
int cur = prices[0];//指针cur遇到比它小的元素替换,遇到比它大的元素进行一次利润的计算,保存最大的利润
for(int i = 1;i < prices.size();i++)
{
if (prices[i] < cur){
cur = prices[i];
continue;
}
else if (prices[i] > cur){
Max = max(Max,prices[i] - cur);
}
}
return Max;
}
};
给你一个整数数组prices
,其中prices[i]
表示某支股票第i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多只能持有一股股票。你也可以先购买,然后在同一天出售。
返回你能获得的最大利润 。
示例 1:
输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
总利润为 4 + 3 = 7 。
示例 2:
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
总利润为 4 。
示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。
思路:
需要注意的是,我们可以在同一天购买和/或出售股票,也就是一天之内可以先抛售股票,再购买股票(可能与实际购买抛售情况不符),因此我们只需要找到所有的“上坡”即可获得最大的利润。
代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
//不限制交易次数的情况下 如何获得最大利润
if(prices.size() == 1) return 0;
//收集所有上坡
int ans = 0;
for (int i = 1;i < prices.size();i++)
{
if (prices[i] > prices[i-1])
{
ans += (prices[i] - prices[i-1]);
}
}
return ans;
}
};
假设有打乱顺序的一群人站成一个队列,数组people
表示队列中一些人的属性(不一定按顺序)。每个people[i] = [hi, ki]
表示第i
个人的身高为hi
,前面正好有 ki
个身高大于或等于hi
的人。
请你重新构造并返回输入数组people
所表示的队列。返回的队列应该格式化为数组 queue
,其中queue[j] = [hj, kj]
是队列中第j
个人的属性(queue[0]
是排在队列前面的人)。
示例 1:
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
这里是引用
示例 2:
输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]
思路:
参照Sunny的题解思路,总体来说就是排序和插入的操作:
代码:
class Solution {
public:
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
sort(people.begin(), people.end(), [](const vector<int>& u, const vector<int>& v) {
return u[0] > v[0] || (u[0] == v[0] && u[1] < v[1]);//相同元素值,K值大的在后面
});
int length = people.size();
vector<vector<int> > ans;
for (int i = 0; i < length; ++i)
{
if (people[i][1] >= i)
ans.push_back(people[i]);
else
ans.insert(ans.begin() + people[i][1], people[i]);//vec.insert(vec.begin()+i,a);在第i+1个元素前面插入a;
}
return ans;
}
};
给你一个长度为n
的整数数组nums
,请你判断在最多改变1
个元素的情况下,该数组能否变成一个非递减数列。
我们是这样定义一个非递减数列的: 对于数组中任意的i (0 <= i <= n-2)
,总满足 nums[i] <= nums[i + 1]
。
示例 1:
输入: nums = [4,2,3]
输出: true
解释: 你可以通过把第一个 4 变成 1 来使得它成为一个非递减数列。
示例 2:
输入: nums = [4,2,1]
输出: false
解释: 你不能在只改变一个元素的情况下将其变为非递减数列。
思路:
这道题极易出现一种错误:仅仅判断是否出现了一次下降,见下方错误代码。此种解法会在[3,4,2,3]
处报错。因此需要仔细思考你的贪心策略在各种情况下,是否仍然是最优解。
问题: 在遍历比较数组中nums[i]
和nums[i-1]
两个数的大小时,如果nums[i]
nums[i]
变为nums[i-1]
还是nums[i-1]
变为nums[i]
。因此需要引入第三个数。
nums[i]>=nums[i-2]
,则nums[i-1]=nums[i]
或nums[i]=nums[i-1]
都可,但是为了“贪心”单调递增,我们希望前面的越小越好,所以nums[i-1]=nums[i]
。nums[i],则nums[i]=nums[i-1]
。
以[3,4,2,3]
为例:当i=2
时nums[2]
nums[2]
nums[2]
变为nums[1]
即4(大于4也可,但是我们不知道后面数字的情况,所以尽量时当前值更小),此时数组变为[3,4,4,3]
,i=3
,有3<4
,nums[3]
也要变为nums[2]
即4,成为非递减数列至少需要变换两次数字,因此返回false
。
代码:
错误代码:
class Solution {
public:
bool checkPossibility(vector<int>& nums) {
int n = nums.size();
if (n <= 1) return true;
int flag = 0;
for(int i = 0;i < n-1;i++)
{
if(nums[i] > nums[i+1]) flag++;
}
return flag == 1 || flag == 0;
}
};
贪心算法:
class Solution {
public:
bool checkPossibility(vector<int>& nums) {
int count = 0;
for (int i = 1; i < nums.size(); i++) {
if (nums[i] < nums[i - 1]) {
if (i == 1 || nums[i] >= nums[i - 2]) {//需要注意i == 1的情况
nums[i - 1] = nums[i];
}
else {
nums[i] = nums[i - 1];
}
count ++;//需要改变的元素数目
}
}
return count <= 1;
}
};
贪心算法的题目到此告一段落,后续如果有做新的题也会陆续补充~欢迎关注收藏哦