代码随想录第34天

1.K次取反后最大化的数组和:

第一个贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。

如果当负数都变成正数后,并且k>0时,又有第二个贪心思路:
局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。

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

我的思路和他差不多,不过完全没想到贪心:

2.加油站:

暴力解法:

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

注:
1.他index设置的是真巧妙,很适合环一圈回到起点就停止的判断 

2.

if (rest >= 0 && index == i) return i;

关于这里的判断条件中rest>=0 ,防止最后一次index==i,但是rest<0,就是最后油量不够回到起点,却出来了,这样应该是不能return i的,所以要加rest>=0;

贪心解法1(不完全是贪心算法):

总结了3种情况:

1.如果gas的总量小于cost的总量,那么不可能有解;

2.如果从0开始遍历,算rest=gas[i]-cost[i]都大于0,则return 0;

3.如果2在遍历的过程中油箱的最小值(注意这里是油箱的最小值不是rest的最小值)是负的,则说明从0到出现那个rest最小值是走不通的,那一段的gas的总量小于cost的总量,所以把遍历的起点逆时针移动,找找看前面多遍历一个点能不能把那个最小值加到大于0,能把这个负数填平的节点就是出发节点

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

贪心算法2:
局部最优:根据每个加油站的gas[i]和cost[i]计算出每个加油站的rest[i],定义一个curSum去累加rest[i],当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1(注意这里是至少,不是一定,如果出现更大的负数,就是更新i,那么起始位置又变成新的i+1了),因为从i之前开始一定不行。全局最优:找到可以跑一圈的起始位置

注:

1.

关于上面局部最优中为什么出现curSum一旦小于0,从i之前开始一定不行:

设假设的位置是x,后面那个curSum<0的位置的是i; 

因为如果出现这种情况,那说明区间1<0,区间2>0,但是上面贪心思路中说了,一旦出现curSum<0,就会更新i变成i+1,curSum重新开始计数,也就是说假设是从x开始计数,curSum是不会小于0的,但是实际是遍历到i时curSum是小于0的,这个是条件,假设和条件相违背了,所以假设是错的。

2.

为什么一旦[0,i] 区间和为负数,起始位置就可以是i+1呢,i+1后面就不会出现更大的负数?

如果出现更大的负数,就是更新i,那么起始位置又变成新的i+1了,直到遍历出一个i,他后面的curSum大于0,这个i就是答案,为什么呢,因为前面的不可能了,而totalSum大于0,说明一定有一个加油站能当作答案,就相当于排除法。

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

3.分发糖果:

问题:

1.出现连续相等的怎么办(自己的代码):

 按我的思路是1 2 3 1 2 2 1 总数为12,答案是1 2 3 1 3 2 1 总数为13,这样其实是有问题的,因为最后一个87是大于2的但是他们的糖果是一样多的,所以是错的。

所以:

1.不能从左到右遍历比较前后两个值的大小进行操作(也就是可能对同一个值进行两次操作)(左大于右和右大于左一起谈论了)

2.改成从右到左一起谈论也不可以:

 结果会变成:

1 1 1 4 3 2 1

贪心思路:

既然就从一个方向遍历并同时讨论左大于右和右大于左不行,那就分开讨论。
先从左到右:

局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果

在从右到左:
局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量既大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。

当然可以反过来

但是注意:

这里反过来指的是可以先从后往前遍历判断左大于右,再从前往后遍历判断右大于左,不是返成这样:先从后往前遍历判断右大于左,再从前往后遍历判断左大于右,这样是错的:

上面的的11 是这样的1 2 2 1 2 2 1 

顺便一提上面错误的先后判断顺序反一下也是错的也就是先从前往后遍历判断左大于右,再从后往前遍历判断右大于左这个思路也是错的。

所以遍历的顺序要和比较的顺序要绑定也就是从左到右遍历一定要绑定右大于左,而从后往前遍历要绑定左大于右

 

例题:

1 2 2 5 4 3 2 

答案是1 2 1 4 3 2 1

问:

为什么在前面已经从前往后遍历判断完右大于左的前提下,判断左孩子大于右孩子的情况一定要从后向前遍历?

因为 rating[5]与rating[4]的比较 要利用上 rating[5]与rating[6]的比较结果,所以 要从后向前遍历。

如果从前向后遍历,rating[5]与rating[4]的比较 就不能用上 rating[5]与rating[6]的比较结果了 。如图:

 

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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值