贪心算法一般分为如下四步:
将问题分解为若干个子问题
找出适合的贪心策略
求解每一个子问题的最优解
将局部最优解堆叠成全局最优解
其实这个分的有点细了,真正做题的时候很难分出这么详细的解题步骤,可能就是因为贪心的题目往往还和其他方面的知识混在一起。
贪心没有套路,说白了就是常识性推导加上举反例。
//
class Solution {
public:
bool canJump(vector& nums) {
int cover = 0;
if (nums.size() == 1) return true; // 只有一个元素,就是能达到
for (int i = 0; i <= cover; i++) { // 注意这里是小于等于cover
【第一次出现】不断更新 for 的终点,类似回溯or动态规划?
cover = max(i + nums[i], cover);
if (cover >= nums.size() - 1) return true; // 说明可以覆盖到终点了//-1,注意此时是下标,所以-1
}
return false;
}
};
45.跳跃游戏II
// // 以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点,这个范围内最小步数一定可以跳到,不用管具体是怎么跳的,不纠结于一步究竟跳一个单位还是两个单位。
// //一步一步(i)遍历整个数组[0~倒数第二位],在每一步记录能到的最大的终点,这个终点最大只能是数组终点,如果不够大,后续步数会追上,此时ans ++
注意和前一题(跳跃游戏1)的 for 等等参数都不同,需要再次对比着看
// class Solution {
// public:
// int jump(vector& nums) {
// int ans = 0; // 记录走的最大步数
// int curDistance = 0; // 当前覆盖的最远距离下标
// int nextDistance = 0; // 下一步覆盖的最远距离下标
// for (int i = 0; i < nums.size() - 1; i++) { // 注意这里是小于nums.size() - 1,这是关键所在
// nextDistance = max(nums[i] + i, nextDistance); // 更新下一步覆盖的最远距离下标
// if (i == curDistance) { // 遇到当前覆盖的最远距离下标
// curDistance = nextDistance; // 更新当前覆盖的最远距离下标
// ans++;
// }
// }
// return ans;
// }
// };
1005. K 次取反后最大化的数组和
// // 第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
// // 第二步:从前向后遍历,遇到负数将其变为正数,同时 K--
// // 第三步:如果 K 还大于0,那么反复转变数值最小的元素,将 K 用完(若 k 大于0,只需考虑是奇数的情况)
// // 第四步:求和
// //局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。局部最优可以推出全局最优
// class Solution {
// static bool cmp(int a, int b) {//【第一次出现! (默认private?)static cmp 】
// return abs(a) > abs(b);//将 sort 的规则改成绝对值的降序排列(从大到小)//return a& A, int K) {
// sort(A.begin(), A.end(), cmp); // 第一步
// for (int i = 0; i < A.size(); i++) { // 第二步
// if (A[i] < 0 && K > 0) {
// A[i] *= -1;
// K--;
// }
// }
// if (K % 2 == 1) A[A.size() - 1] *= -1; // 第三步
// int result = 0;
// for (int a : A) result += a; // 第四步
// return result;
// }
// };
class Solution{
static bool cmp (int a, int b) {
return abs(a)>abs(b);///函数不像 if 必须有大括号
}
public:
int largestSumAfterKNegations(vector& nums, int K){
sort(nums.begin(), nums.end(), cmp);
for(int i = 0; i < nums.size(); i ++){
if (nums[i] < 0 && K > 0) {//还是加上{}保险。。。
nums[i] *= -1;///&& K > 0
K--;
}
}
if (K%2 == 1) nums[nums.size()-1] *= -1;
int sum = 0;
for(int i = 0; i < nums.size(); i++){
sum += nums[i];
}
//for (int a : nums) sum += a; //更简单!
return sum;
}
};
134. 加油站
class Solution {
public:
int canCompleteCircuit(vector& gas, vector& cost) {
int curSum = 0;
int totalSum = 0;
int start = 0;
for (int i = 0; i < gas.size(); i++) {
curSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if (curSum < 0) { // 当前累加res t[i]和 curSum一旦小于0
start = i + 1; // 起始位置更新为i+1
curSum = 0; // curSum 从0开始
}
}
if (totalSum < 0) return -1; // 说明怎么走都不可能跑一圈了
return start;
}
};
//首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest [i]相加一定是大于等于零的。
//如果gas 的总和小于cost 总和,那么无论从哪里出发,一定是跑不了一圈的
//why ??需要重理解。目前理解只能是举不出反例。
//有一个环形路上有n个站点; 每个站点都有一个好人或一个坏人; 好人会给你钱,坏人会收你一定的过路费,如果你带的钱不够付过路费,坏人会跳起来把你砍死; 问:从哪个站点出发,能绕一圈活着回到出发点?(vectorint 不能直接相减)
135. 分发糖果
// 这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。
// 此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
// ///相同评分的相邻孩子,可以获得不同数量的糖果(从而可以2,1,1,1,2,减少花费的糖果总数
// 再确定左孩子大于右孩子的情况(从后向前遍历)
// 遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?
// 因为如果从前向后遍历,根据 ratings [i + 1] 来确定 ratings [i] 对应的糖果,那么每次都不能利用上前一次的比较结果了。局部最优:取candyVec [i + 1] + 1 和 candyVec [i] 最大的糖果数量,保证第i个小孩的糖果数量即大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。
// 局部最优可以推出全局最优。
// 所以就取 candyVec [i + 1] + 1 和 candyVec [i] 最大的糖果数量,candyVec [i]只有取最大的才能既保持对左边candyVec [i - 1]的糖果多,也比右边 candyVec [i + 1]的糖果多。
// 这在leetcode上是一道困难的题目,其难点就在于贪心的策略,如果在考虑局部的时候想两边兼顾,就会顾此失彼。
// 那么本题我采用了两次贪心的策略:
// 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
// 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。
// 这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。
class Solution {
public:
int candy(vector& ratings) {
vector candyVec(ratings.size(), 1);//【第一次见】初始化vector的初始值和大小为1
// 从前向后
for (int i = 1; i < ratings.size(); i++) {//右大改右,跳过第一个
if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;
}
// 从后向前
for (int i = ratings.size() - 2; i >= 0; i--) {//左大改左,跳过最后一个
if (ratings[i] > ratings[i + 1] ) {candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);}
}
// 统计结果
int result = 0;
for (int i = 0; i < candyVec.size(); i++) result += candyVec[i];
return result;
}
};
860.柠檬水找零
// 情况一:账单是5,直接收下。
// 情况二:账单是10,消耗一个5,增加一个10
// 情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
// 此时大家就发现 情况一,情况二,都是固定策略,都不用我们来做分析了,而唯一不确定的其实在情况三。
// 所以局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。
class Solution {
public:
bool lemonadeChange(vector& bills) {
int five = 0, ten = 0, twenty = 0;
for (int bill : bills) {
// 情况一
if (bill == 5) five++;
// 情况二
if (bill == 10) {
if (five <= 0) return false;
ten++;
five--;
}
// 情况三
if (bill == 20) {
// 优先消耗10美元,因为5美元的找零用处更大,能多留着就多留着
if (five > 0 && ten > 0) {
five--;
ten--;
twenty++; // 其实这行代码可以删了,因为记录20已经没有意义了,不会用20来找零
} else if (five >= 3) {
five -= 3;
twenty++; // 同理,这行代码也可以删了
} else return false;
}
}
return true;
}
};
406.根据身高重建队列
在这里插入代码片



