代码随想录算法训练营DAY34|C++贪心算法Part.3|1005.K次取反后最大化的数组和、134.加油站、135.分发糖果

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;
}

贪心算法

首先我们必须拆解成一个局部问题。

但是我们首先要确定的就是,总油量减去总消耗大于等于零那么一定可以跑完一圈

那么我们先列一下剩余油量列表:

明确:油箱无限大,是可以累积的。从下标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;
    }
};
  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值