目录
1、贪心算法简单题
①、分发饼干
②、 按k次取法的最大值
③、柠檬水找零
小结
2、贪心算法中等题
①、摆动序列
②、单调递增数字
3、贪心解决股票问题
①、买卖股票最佳时机Ⅱ
②、买卖股票最佳时机含手续费
4、双维度贪心问题
①、分发糖果
②、根据身高重建队列
小结
5、贪心解决区间问题
①、跳跃游戏
题目概述:
解题思路:
代码
②、跳跃游戏Ⅱ
题目概述
解题思路
代码
③、最少的箭引爆气球
题目概述
解题思路
代码
④、无重叠区间
题目概述
解题思路
代码
⑤、划分字母区间
题目概述
解题思路
代码
⑥、合并区间
题目概述
解题思路
代码
小结
7、其它贪心题目
①、最大子序列和
题目概述
解题思路
代码
②、加油站
题目概述
解题思路
代码
小结
总结
题目链接:分发饼干
该题目就是将两个数组从小到大排序,然后贪心就是尽可能用大的配大的,小的配小的,然后直接进行比较。从后往前遍历。
int findContentChildren(vector& g, vector& s) {
if(s.size()==0) return 0;
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int j = s.size()-1;int tem = 0;
for(int i = g.size()-1;i>=0;i--){
if(s[j]>=g[i]){
j--;tem++;
}
if(j<0)break;
}
return tem;
}
题目链接:k次取反最大值
该题目就是依旧排序,然后根据绝对值大小进行降序排序,然后有负的就从大到小取反,之后如果k剩奇数个,就对最小的数取反即可。
static bool cmp(int a, int b)
{
return abs(a) > abs(b);
}
int largestSumAfterKNegations(vector& nums, int k) {
sort(nums.begin(), nums.end(), cmp);
int res = 0;
for(int i = 0; i 0){
k--;
nums[i] *= -1;
}
}
if(k % 2 == 1) nums[nums.size() - 1] *= -1;
for(int i = 0; i
题目链接:柠檬水找零
该题目主要就20的处理逻辑稍微麻烦一些,
5块钱。直接five+1就可以。
10块钱,直接five-1,ten + 1就可以。
20块钱,就分为找三个5块和一个五块一个10块。(这里也体现我们的贪心思路)
如果有10肯定优先使用10,因为10比起5块,通用性更差,我们贪心就是局部最优,优先使用通用性更差的。
bool lemonadeChange(vector& bills) {
int five = 0, ten = 0, twelve = 0;
for(int i = 0; i 0)//优先使用面值大的,面值小的价值更高
{
if(five > 0){
five --; ten --;
}
else return false;
}
else{
if(five >=3){
five -= 3;
}
else return false;
}
twelve ++;
}
}
return true;//所有情况都遍历了,均满足
}
一般碰到无序的贪心类题目,先从小到大(从大到小)进行排序,然后通过局部最优求解全局最优,想①和②都是这种类型的。
题目链接:摆动序列
需要讨论两种情况:1、两个数算不算有坡度,这里根据测试数据,算有。因此为二,但是因为两个数据只能比一次,因此res初始化成1。
2、单调序列有坡度,就是平的,此时,我们就pre存储住,平坡左边的状态,然后跟右边状态对比。
int wiggleMaxLength(vector& nums) {
if(nums.size() < 2) return nums.size();
int cur = 0, pre = 0, res = 1;
for(int i = 1; i 0 && pre <= 0) || (cur < 0 && pre >= 0)){
res++;
pre = cur;
}
}
return res;
}
题目链接:单调递增数字
我们先将数字各个位置按照从小到大(低位 -> 高位)存储,不满足条件的,就低位的变成9,高一位的减一,之后再重新加出来。
int monotoneIncreasingDigits(int n) {
if(n < 10) return n ;
vector res;
int tem = n;
while(n){
res.push_back(n % 10);
n /= 10;
}
bool ct = false; int index = 0;
for(int i = 0; i < res.size() - 1; i++){
if(res[i] < res[i + 1]){
res[i + 1] --;
ct = true;
index = i;
}
}
for(; index >= 0; index--){
res[index] = 9;
}
if(!ct) return tem;
tem = 0;
for(int i = 0; i < res.size(); i++){
tem += res[i] * pow(10, i);
}
return tem;
}
题目链接:买卖股票最佳时机Ⅱ
这个其实非常简单,将两天之间的利润拆分开来,将其中正的相加。然后输出最终结果。
int maxProfit(vector& prices) {
int pre = 0;int cur = 0;
int sum = 0;
for(int i = 0;i0){
sum+=(pre-cur);
}
}
return sum;
}
题目链接:买卖股票含手续费
int maxProfit(vector& prices, int fee) {
int result = 0;
int minPrice = prices[0]; // 记录最低价格
for (int i = 1; i < prices.size(); i++) {
// 情况二:相当于买入
if (prices[i] < minPrice) minPrice = prices[i];
// 情况三:保持原有状态(因为此时买则不便宜,卖则亏本)
if (prices[i] >= minPrice && prices[i] <= minPrice + fee) {
continue;
}
// 计算利润,可能有多次计算利润,最后一次计算利润才是真正意义的卖出
if (prices[i] > minPrice + fee) {
result += prices[i] - minPrice - fee;
minPrice = prices[i] - fee; // 情况一,这一步很关键,避免重复扣手续费
}
}
return result;
}
题目链接:分发糖果
本题就是先从左到右进行比较,然后再从右往左比较,最后就是最终的结果。
int candy(vector& ratings) {
int res = 0;
vector nums(ratings.size(), 1);
for(int i = 0; i ratings[i]){
nums[i + 1] = nums[i] + 1;
}
}
for(int i = ratings.size() - 1; i >0; i--){
if(ratings[i - 1] > ratings[i] && nums[i-1] <= nums[i]){
nums[i - 1] = nums[i] + 1;
}
}
for(int i = 0 ;i < nums.size(); i++) res += nums[i];
return res;
}
题目链接:根据身高重建队列
本题就是重点是排序,排序将身高大的排前面,然后,他们数值相同的posion按从小到大排序,然后,插入的时候就按照posion一次插入即可。
static bool cmp(vector& a,vector& b)
{
if(a[0]==b[0]) return a[1]b[0];
}
vector> reconstructQueue(vector>& people) {
sort(people.begin(),people.end(),cmp);
list> que;
for(int i = 0;i>::iterator it = que.begin();
while(posion--){
it++;
}
que.insert(it,people[i]);
}
return vector> (que.begin(),que.end());
}
双维度问题,通常需要从左边考虑一次,从右边考虑一次,通常就能解的题目,贪心就是要多尝试和假设。
题目链接:跳跃游戏
给定一个数组,然后数组中数字表示能跳的格子数,如果能跳到最后一个格子,就true,否则就false。
本题没有数跳的次数,因此跳次数不重要,重要的是覆盖范围,每个数,都有覆盖范围,我们再有效范围内记录这些范围,然后遍历,直到找到到达甚至超越边界的数,如果遍历完都没有,就返回false。
bool canJump(vector& nums) {
int mcover = 0;
for(int i = 0; i <= mcover; i++){
mcover = max(mcover, i + nums[i]);
if(mcover >= nums.size() - 1) return true;
}
return false;
}
题目链接:跳跃游戏Ⅱ
该题目就是在上一题目基础上添加了 移动最少步数 的限定。
本题依然化用贪心的思路,我们定义一个cur变量表示:当前元素能到达的最远处,再定义一个pre变量表示:下一个元素能到达的最远处,如果i == cur,步数加一,如果cur >= nums.size() - 1。说明找到了结果
int jump(vector& nums) {
if(nums.size() == 1) return 0;
int cur = 0; int pre = 0; int ans = 0;
for(int i = 0; i= nums.size() - 1) break;
}
return ans;
}
题目链接:最少的箭引爆气球
给了一堆气球在x轴的左右坐标(相当于气球的宽度),然后有的气球是重叠在一起的,因此使用箭引爆,从而求最少的箭数。
这种题我们一般选定气球的左半边或者右半边进行排序,定义一个气球坐标为[x1, x2]。
如果下一个气球的x1 > 现在气球的 x2,那我们就应该res + 1。如果,小于等于,说明重叠,然后,我们右边界就更新成两个气球中最小的。
static bool cmp(vectora, vector b)
{
return a[0] < b[0];
}
int findMinArrowShots(vector>& points) {
int res = 1;
sort(points.begin(), points.end(), cmp);
for(int i = 0; i < points.size() - 1; i++){
if(points[i + 1][0] > points[i][1]){
res ++;
}
else {
points[i + 1][1] = min(points[i][1], points[i + 1][1]);
}
}
return res;
}
题目链接:无重叠区间
给点数个区间,每个区间有两个坐标(闭区间),然后我们进行移除,使得所有区间没有交际,要求移除个数最少。
依然是先排序,排序有两种方法。
1、右排序,记录非交叉区间,然后总区间减去非交叉区间。
2、左排序,记录交叉区间,然后直接记录重叠区间。
左排序代码
static bool cmp(vector a, vector& b)
{
return a[0] < b[0];
}
int eraseOverlapIntervals(vector>& intervals) {
if(intervals.size() == 0) return 0;
int res = 0;
sort(intervals.begin(), intervals.end(), cmp);
for(int i = 0; i
右排序代码
static bool cmp(vector a, vector b)
{
return a[1] < b[1];
}
int eraseOverlapIntervals(vector>& intervals) {
if(intervals.size() == 0) return 0;
sort(intervals.begin(), intervals.end(), cmp);
int res = 1;
for(int i = 1; i < intervals.size(); i++){
if(intervals[i][0] >= intervals[i - 1][1]){
res ++;
}
else {
intervals[i][1] = min(intervals[i][1], intervals[i-1][1]);
}
}
return intervals.size() - res;
}
题目链接:划分字母区间
给定一串字母,然后将字母划分成好几段,然后每一段中都只有出现过的数字,最后返回每一段的字母个数。
本题既然跟出现次数有关,我们就用哈希表记录,然后记录该字母最后一次出现的最远坐标在哪里。最终用left和right记录区间,用两者差(+1是因为包含起点)表示区间长度。
vector partitionLabels(string s) {
int hash[27];
vector res;
for(int i = 0; i
题目链接:合并区间
给定好几个区间,将其中重叠的区间合并,并且返回最终没有重叠区域的集合。
首先碰到这种给了好几个区间的题目,一定要排序。我们直接左边排序。
然后我们直接将一个区间放入res中,然后每次判断res.back()[1]和当前数组关系,重叠直接合并,不重叠再收纳。
vector> merge(vector>& intervals) {
if(intervals.size() == 0) return intervals;
vector> res;
sort(intervals.begin(), intervals.end(), cmp);
res.push_back(intervals[0]);
for(int i = 1; i
区间类型的题目,很多都是涉及两个坐标,一左一右,而一般我们都是对区间先排序,然后再看下一步,同时判断重叠区间的时候,一般涉及到更新边界,只要将边界更新好,就可以接触这类型的题目。
给定一个数组,找到其中拥有最大和的子数组。
我们用贪心的思路,只要保证每次相加,前面的数是正的,那么对于后面的数来说就是正反馈,是可以试着往下加。
为甚不看后面的数的正负,是因为有可能后面的后面会更大,但是前面的数已经成定局,因此前面的正负一般能推出后面是否是最大值。
int maxSubArray(vector& nums) {
int tem = 0;
int res = - 1e9 + 10;
for(int i = 0; i res) res = tem;
if(tem < 0) tem = 0;
}
return res;
}
给定一个gas数组和cost数组,gas表示该加油站能获得的汽油数量,cost表示前往紧邻的加油站所消耗的汽油升数。
整理以下,其实数组用gas - cost数组构建的新数组就是汽油的净变化量。
然后我们记录每次相加的值,以及记录下标。
int canCompleteCircuit(vector& gas, vector& cost) {
int cur = 0; int res = 0;
int sum = 0;
for(int i = 0; i
其它的题目就要根据题意,仔细分析给出的数据作用,然后按照思路慢慢求解。
谈心类的题目,很多都没有规律,但是我们做多了又会发现贪心有时候真的很好用,我们一般贪心可以试试排序,试着画一下折线图,找规律,因为贪心就类似脑筋急转弯,有时候说不定就思路涌现。