文章目录
1005.K次取反后最大化的数组和
文章讲解:1005.K次取反后最大化的数组和
视频讲解:贪心算法,这不就是常识?还能叫贪心?LeetCode:1005.K次取反后最大化的数组和
状态:
思路
很直观的想法:
局部最优——让绝对值大的负数变为正数,当前数值达到最大;
整体最优——整个数组和达到最大。
现在要考虑第二个阶段,如果负数已经都变为正数了,我们仍然需要进行反转,那么就是另外一个贪心了。
局部最优——只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1},反转1得到-1比反转5得到-5要好太多。
全局最优:整个数组和达到最大
伪代码
- 将数值按照绝对值从大到小排序。
static bool cmp(int a, int b) {
return abs(a) > abs(b);
}
sort(A.begin(), A.end(), cmp);
- 从前向后遍历,遇到负数将其变为正数,同时k–
for (int i = 0; i < A.size(); i++){
if (A[i] < 0 && K > 0){
A[i] *= -1;
K--;
}
}
- 如果k还大于0,那么反复转变数值最小的元素「
A.back()
」,直到k=0。这里要用一个技巧,如果k是2的倍数,A.back
不变,K如果是奇数,那么A.back()
就变成了-A.back()
。
if (K % 2 == 1){
A[A.size() - 1] *= -1;
}
- 求和
for (int a : A) result += a;
return result;
CPP代码
class Solution {
static bool cmp(int a, int b) {
return abs(a) > abs(b);
}
public:
int largestSumAfterKNegations(vector<int>& 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;
}
};
134.加油站
文章讲解:134.加油站
视频讲解:贪心算法,得这么加油才能跑完全程!LeetCode :134.加油站
状态:
思路
暴力解法
很直观的一个暴力解法情况,我们遍历每一个加油站为起点的情况,模拟一圈。
如果跑了一圈中途没有断油,并且最后的油量大于等于0,说明这个起点是ok的。
这里总结一个:for循环适合从头到尾的遍历,while适合模拟环形遍历
- 首先遍历耗油量
for (int i = 0; i < cost.size(); i++){
}
- 记录剩余的油量,设置模拟时的起点位置,由于加油站是环形分布的,比如当i事最后一个加油站时,下一个加油站应该是第一个加油站,后面会体现出该代码的的含义。
int rest = gas[i] - cost[i];
int index = (i + 1) % cost.size();
- 模拟以i为起点行驶一圈
while (rest > 0 && index != i){
rest += gas[index] - cost[index];
index = (index + 1) % cost.size();
}
- 如果以i为起点跑一圈,剩余油量>=0, 返回该起点位置
if (rest >= 0 && index == i) return i;
直接全局硬分析,然后用代码模拟
这就考验我们的分析能力了:
- 如果gas总和小于cost总和,那么无论从哪出发,一定都跑不了一圈。当然了,就算大于等于cost也不一定就能跑完一圈。
rest[i] = gas[i] - cost[i]
为一天剩下的油,那么i从0开始计算,累加到最后一站,如果累加没有出现负数,说明从0出发就可以。- 这里的
min
表示的是跑圈过程中,剩余油量的最小值,如果是负数,说明不能从0出发,后续还要进行判断 - 如果大于等于零,说明我们从0出发就可以了。
- 这里的
int curSum = 0;
int min = INT_MAX; //从起点出发,油箱里的油量最小值
for (int i = 0; i < gas.size(); i++){
int rest = gas[i] - cost[i];
curSum += rest;
if (curSum < min) {
min = curSum;
}
}
//情况一
if (curSum < 0) return -1;
//情况二
if (min >= 0) return 0;
-
如果累加到
min
为负数,那么我们从非零结点出发,从后向前开车,看哪个结点能把这个负数填平,说明我们从这个结点出发就能跑完一圈。(该过程本质上就是在模拟跑圈的过程,)
for (int i = gas.size() - 1; i >= 0; --i){
int rest = gasi[i] - cost[i];
min += rest;
if (min > 0) {
return i;
}
return -1;
}
贪心算法
首先我们必须拆解成一个局部问题。
但是我们首先要确定的就是,总油量减去总消耗大于等于零那么一定可以跑完一圈。
那么我们先列一下剩余油量列表:
![](https://img-blog.csdnimg.cn/direct/b47a5c6a69cc44de8be110f5c39be16d.png#pic_center)
明确:油箱无限大,是可以累积的。从下标2的位置可以看出,我们只能通过2后面的下标开始收集油,这样才能有希望突破2这个位置。
那么
局部最优:当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。
全局最优:找到可以跑一圈的起始位置。
int curSum = 0; //表示累加的剩余量
int totalSum = 0; //用来判断总油量和总消耗量是否大于等于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){
start = i + 1;
curSum = 0;
}
}
判断如下:
//不满足总油量减去总消耗大于等于零肯定跑不完
if (totalSum < 0) return -1;
return start;
CPP代码
暴力解法
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
for (int i = 0; i < cost.size(); i++) {
int rest = gas[i] - cost[i]; // 记录剩余油量
int index = (i + 1) % cost.size();
while (rest > 0 && index != i) { // 模拟以i为起点行驶一圈(如果有rest==0,那么答案就不唯一了)
rest += gas[index] - cost[index];
index = (index + 1) % cost.size();
}
// 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
if (rest >= 0 && index == i) return i;
}
return -1;
}
};
全局硬分析解法
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int curSum = 0;
int min = INT_MAX; // 从起点出发,油箱里的油量最小值
for (int i = 0; i < gas.size(); i++) {
int rest = gas[i] - cost[i];
curSum += rest;
if (curSum < min) {
min = curSum;
}
}
if (curSum < 0) return -1; // 情况1
if (min >= 0) return 0; // 情况2
// 情况3
for (int i = gas.size() - 1; i >= 0; i--) {
int rest = gas[i] - cost[i];
min += rest;
if (min >= 0) {
return i;
}
}
return -1;
}
};
贪心
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& 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) { // 当前累加rest[i]和 curSum一旦小于0
start = i + 1; // 起始位置更新为i+1
curSum = 0; // curSum从0开始
}
}
if (totalSum < 0) return -1; // 说明怎么走都不可能跑一圈了
return start;
}
};
135.分发糖果
文章讲解:135.分发糖果
视频讲解:贪心算法,两者兼顾很容易顾此失彼!LeetCode:135.分发糖果
状态:就会审下题,如果相邻小孩分数一样,糖果也可以给一个多一个少
思路
其实有一个很直接的思路就是,我们每次都照顾每个孩子的左右两边。这样代码会很复杂,思路也不好理清。这里直接给出答案
先确定右边评分大于左边的情况(从前向后遍历);然后确定左边评分大于右边的情况(从后向前遍历):
局部最优——从左向右遍历,只有右边评分比左边大,右边孩子就多一个糖果;然后从右向左遍历,左边评分比右边大,左孩子就多一个糖果。
全局最优——这样推广到全局,先左边模拟一遍,再右边模拟一遍,发现答案正确!
抽象出来,本题的局部最优并不是某个孩子的左右孩子比较,而是先一直从左往右比较,然后为了利用上这个遍历结果,再全部从右往左比较。「原因详细可看代码随想录文章讲解的图片和思路:文章讲解」
伪代码
先确定右边评分大于左边的情况(也就是从前向后遍历)
// 从前向后
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);
}
}
CPP代码
class Solution {
public:
int candy(vector<int>& ratings) {
vector<int> candyVec(ratings.size(), 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;
}
};