代码随想录Day29-贪心:力扣第45m、1005e、134m、135h题

45m. 跳跃游戏 II

题目链接
代码随想录文章讲解链接

方法一:贪心(自己想的)

用时:10m10s

思路

当前格子curIdx,当前格子的最大跳跃距离nums[curIdx],选择下一步的格子nextIdx,下一步格子的最大跳跃距离nums[nextIdx],在curIdx位置选择nextIdx的贪心逻辑:使nextIdx+nums[nextIdx]最大,这样每一步都能保证是局部最优,即让接下来的两步跳的最远。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int jump(vector<int>& nums) {
        if (nums.size() < 2) return 0;

        int curIdx = 0;
        int res = 0;
        while (curIdx + nums[curIdx] < nums.size() - 1) {
            int maxIdx = -1;
            int nextIdx = -1;
            for (int i = curIdx + 1; i <= curIdx + nums[curIdx]; ++i) {
                if (i + nums[i] >= maxIdx) {
                    maxIdx = i + nums[i];
                    nextIdx = i;
                }
            }
            curIdx = nextIdx;
            ++res;
        }
        return res + 1;
    }
};

方法二:贪心

用时:1m29s

思路

本质上与方法一一致,都是计算两步的最大距离,但是方法二的实现更简洁。用curDistance记录上一个格子能到达的最大距离,用maxDistance记录当前的最大距离,只有当遍历的位置到达curDistance时,才表明超出上一个格子能跳跃到达的最大范围,此时跳跃步数加一,并更新curDistance

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int jump(vector<int>& nums) {
        int curDistance = 0;
        int maxDistance = 0;
        int res = 0;
        for (int i = 0; i < nums.size() - 1; ++i) {
            maxDistance = max(maxDistance, i + nums[i]);
            if (i == curDistance) {
                curDistance = maxDistance;
                ++res;
            }
        }
        return res;
    }
};

看完讲解的思考

无。

代码实现遇到的问题

无。


1005e. K次取反后最大化的数组和

题目链接
代码随想录文章讲解链接

方法一:排序+贪心

用时:14m1s

思路

贪心逻辑:若取反时总是选择最小的数取反。
先将数组排序,然后执行贪心逻辑取反。

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn)
  • 空间复杂度: O ( log ⁡ n ) O(\log n) O(logn)
C++代码
class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        int res = 0;

		// 排序
        sort(nums.begin(), nums.end());

		// 将较小的负数取反
        for (int i = 0; i < nums.size(); ++i) {
            if (k == 0 || nums[i] >= 0) break;
            nums[i] *= -1;
            --k;
        }

  		// 如果剩余的k为奇数,则把最小的非负数取反
        if (k & 1) {
            int minNum = 101;
            for (int& n : nums) {
                res += n;
                if (n < minNum) minNum = n;
            }
            res -= minNum * 2;
        } else for (int& n : nums) res += n;
        return res;
    }
};

看完讲解的思考

无。

代码实现遇到的问题

无。


134m. 加油站

题目链接
代码随想录文章讲解链接

方法一:贪心(自己想的)

用时:32m22s

思路

贪心逻辑:若当前油量低于0,则说明从上一个起始位置出发无法环路一周,且不可能从先前的位置出发,所以起始位置只可能是当前位置之后的加油站。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
    	// 若汽油总量小于总开销,则肯定无法环路一周
        int totalGas = 0;
        for (int& g : gas) totalGas += g;
        for (int& c : cost) totalGas -= c;
        if (totalGas < 0) return -1;
        
        // 遍历各个加油站
        int res = 0;
        int curGas = 0;  // 当前油量
        for (int i = 0; i < gas.size(); ++i) {
            curGas += gas[i] - cost[i];  // 更新油量
            if (curGas < 0) {  // 若当前油量低于0,则说明从上一个起始位置出发无法环路一周
                res = i + 1;  // 将起始位置改为下一个加油站
                curGas = 0;  // 重新开始统计
            }
        }
        return res;
    }
};

方法二:贪心

思路

方法一中统计总油量和总开销可以放在一个循环中进行。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int totalGas = 0;
        int res = 0;
        int curGas = 0;
        for (int i = 0; i < gas.size(); ++i) {
            totalGas += gas[i] - cost[i];
            curGas += gas[i] - cost[i];
            if (curGas < 0) {
                res = i + 1;
                curGas = 0;
            }
        }
        return totalGas >= 0 ? res : -1;
    }
};

看完讲解的思考

无。

代码实现遇到的问题

无。


135h. 分发糖果

题目链接
代码随想录文章讲解链接

方法一:

用时:1h13m12s

思路

贪心逻辑:先分别求出满足左侧和右侧要求的糖果数(局部最优),再根据两组糖果数得到满足两侧要求的糖果数(全局最优)。

  1. 先从左向右遍历,若当前孩子的得分高于左边的孩子,根据题目要求,当前孩子的糖果数等于左边孩子糖果数加一。遍历完后就能得到满足左侧要求的糖果数。
  2. 同理,从右向左遍历,就能得到满足右侧要求的糖果数。
  3. 然后根据对应位置的糖果,得到最终的糖果数,对于某个位置的孩子,其糖果数等于满足左侧要求和右侧要求的糖果数中的最大值。
  4. 最后再统计糖果总数。

代码实现时,2、3、4步可以合并。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
    int candy(vector<int>& ratings) {
        int size = ratings.size();
        vector<int> candyNum(size, 1);
        int res = 0;

        // 从左往右遍历,记录满足单侧要求的糖果数
        for (int i = 1; i < size; ++i) candyNum[i] = ratings[i] > ratings[i - 1] ? candyNum[i - 1] + 1 : 1;
        // 从右往左遍历,更新满足两侧要求的糖果数,并计算数量和
        for (int i = size - 2; i >= 0; --i) {
            candyNum[i] = max(candyNum[i], ratings[i] > ratings[i + 1] ? candyNum[i + 1] + 1 : 1);
            res += candyNum[i];
        }
        return res + candyNum[size - 1];
    }
};

方法二:一次遍历

思路

不用拘泥于计算每个位置要分配多少糖果数,只需统计递增序列和递减序列的长度即可。
对于递增序列,新增孩子的糖果数就是递增序列的长度。
对于递减序列,只要递减序列的长度加一,那么总糖果数就会增加序列的长度值,(自己通俗的讲法:因为不知当前孩子是递减序列的倒数第几个,那就先设置为1,如果后续还是递减序列,那么全体递减序列糖果数加一,这样第一个孩子由于是最先入列的,所以肯定是最高的,可以想象成所有人都在长高。)要注意的点是当递增序列的长度和递减序列的长度相等的情况。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int candy(vector<int>& ratings) {
        int size = ratings.size();
        int increase = 1;  // 当前递增序列的长度
        int decrease = 0;  // 当前递减序列的长度
        int pre = 1;  // 上一个孩子的糖果数
        int res = 1;

        for (int i = 1; i < size; ++i) {
            if (ratings[i] >= ratings[i - 1]) {
                decrease = 0;  // 当前不是递减序列,递减序列长度置零
                pre = ratings[i] > ratings[i - 1] ? pre + 1 : 1;  // 若当前孩子分数比前一个高,则糖果数为前一个孩子的糖果数加1,否则置为1
                res += pre;  // 总糖果数加上当前孩子的糖果数
                increase = pre;  // 递增序列的长度与当前孩子的糖果数是相等的
            } else {  // 递减序列
                ++decrease;
                if (increase == decrease) ++decrease;  // 当递增序列的长度和递减序列的长度一致时,此时的递减序列并没有算上极大值点,由于之前递减序列的长度小于递增序列,极大值处的糖果数由递增序列决定;当递减序列长度超过递增序列后,极大值处的糖果数就由递减序列决定了,所以此时递减序列的长度需要加上极大值点
                res += decrease;  // 递减序列每增加一个长度,则每个位置的糖果数都需要加一,所以糖果数增加量为递减序列的长度
                pre = 1;  // 递减序列新增的孩子的糖果数恒定为1
            }
        }
        return res;
    }
};

看完讲解的思考

方法二真妙啊…太牛了…太牛了…

代码实现遇到的问题

自己磕磕绊绊、缝缝补补地写了一大堆,正确解法真简洁。


最后的碎碎念

逐渐找到做贪心题的感觉,但有些贪心题也是真难想啊,感觉贪心题最考智商了,真的是想得到就有,想不到的话一点办法也没有,完全没有套路可言。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目描述: 给你两个版本号 version1 和 version2 ,请你比较它们。 版本号由一个或多个修订号组成,各修订号由一个 '.' 连接。每个修订号由多位数字组成,可能包含前导零。每个版本号至少包含一个字符。修订号从左到右编号,下标从0开始,最左边的修订号下标为0 ,下一个修订号下标为1,以此类推。例如,2.5.33 和 0.1 都是有效的版本号。 比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较忽略任何前导零后的整数值。也就是说,修订号1和修订号001相等。如果版本号没有指定某个下标处的修订号,则该修订号视为0。例如,版本1.0 小于版本1.1,因为它们下标为0的修订号相同,而下标为1的修订号分别为0和1,0 < 1。 返回规则如下: 如果 version1 > version2 返回 1, 如果 version1 < version2 返回 -1, 否则返回 0。 示例 1: 输入:version1 = "1.01", version2 = "1.001" 输出:0 解释:忽略前导零,"01" 和 "001" 都表示相同的整数 "1" 示例 2: 输入:version1 = "1.0", version2 = "1.0.0" 输出:0 解释:version1 没有指定下标为 2 的修订号,即视为 "0" 示例 3: 输入:version1 = "0.1", version2 = "1.1" 输出:-1 解释:version1 中下标为 0 的修订号是 0,version2 中下标为 0 的修订号是 1 。0 < 1,所以 version1 < version2 示例 4: 输入:version1 = "1.0.1", version2 = "1" 输出:1 示例 5: 输入:version1 = "7.5.2.4", version2 = "7.5.3" 输出:-1 提示: 1 <= version1.length, version2.length <= 500 version1 和 version2 仅包含数字和 '.' version1 和 version2 都是 有效版本号

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值