【代码随想录训练营第42期 Day27打卡 贪心Part2 - LeetCode 122.买卖股票的最佳时机II 55. 跳跃游戏 45.跳跃游戏II 1005.K次取反后最大化的数组和

目录

一、做题心得

二、题目与题解

题目一:122.买卖股票的最佳时机II 

题目链接

题解:贪心

题目二:55. 跳跃游戏

题目链接

题解:贪心

题目三:45.跳跃游戏II

题目链接

题解:贪心

题目四:1005.K次取反后最大化的数组和 

题目链接

题解:排序+贪心

三、小结


一、做题心得

感觉贪心这一块不是很容易想到思路,有时候思路来了写下来就很简单,有时候是真难做出来。今天是贪心章节的Part2,共完成了四道相关题目,感觉还是挺有难度的,尤其是对于没有接触过这些类型的题的人。个人感觉贪心是基础算法里偏难的部分了。

话不多说,直接开始今天的内容。

二、题目与题解

题目一:122.买卖股票的最佳时机II 

题目链接

122. 买卖股票的最佳时机 II - 力扣(LeetCode)

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3。
最大总利润为 4 + 3 = 7 。

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
最大总利润为 4 。

示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0。

提示:

  • 1 <= prices.length <= 3 * 104
  • 0 <= prices[i] <= 104
题解:贪心

贪心的题重在于想到如何实现局部最优。 

实现局部最优要求我们只收集正利润。

这道题分三种情况:

        1.单独交易日:记今天价格p1,明天价格p2,那么明天可卖出利润p2 - p1(为负值的话表示亏损)

        2.连续上涨交易日:这时候第一天买最后一天卖收益最大,即pn - p1,等价于(p2 - p1) + (p3 - p2)...(pn - pn-1),也就是可以把跨越多天的买卖等价理解成多个相邻两天的买卖

        3.连续下降交易日:此时买卖肯定亏损,即不买卖

我们可以把以上情况都等价为相邻两天的买卖的堆加。第二天价格大于第一天,那么利润为正,第一天买,第二天卖。

代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        int ans = 0;
        for (int i = 0; i < n - 1; i++) {
            if (prices[i + 1] > prices[i]) {        //注意:我们可以把跨越多天的买卖等价理解成多个相邻两天的买卖
                ans += prices[i + 1] - prices[i];
            }
        }
        return ans;
    }
};

题目二:55. 跳跃游戏

题目链接

55. 跳跃游戏 - 力扣(LeetCode)

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。

示例 1:

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例 2:

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

提示:

  • 1 <= nums.length <= 104
  • 0 <= nums[i] <= 105
题解:贪心

这道题如果是第一次做的话,感觉会很难想到。

随着当前位置的遍历,我们需要不断更新最远可跳达的位置maxpos(每次更新都通过比较,取之间节点i所能跳跃的最大距离i + nums[i]),并找到遍历提前终止的条件即(i > maxpos)当前位置超出了我们所能到达的最大位置,这时它无论如何都跳不到i当前的位置。

感觉这样形容也不容易理解,这里建议直接看代码(含注释):

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n = nums.size();
        int maxpos = 0;              //maxpos表示从数组的开始位置(索引0)到当前遍历到的位置(索引i)为止,能够跳跃到达的最远位置的索引
        for (int i = 0; i < n; i++) {
            if (i > maxpos) {            //如果当前位置超出了我们能够到达的最远位置,则无法到达该位置,更无法到达数组的最后一个位置
                return false;
            }
            else {                      //0 <= i <= maxpos时,maxpos表示该区间内所有元素中能跳到的最远位置
                maxpos = max(maxpos, i + nums[i]);         //不断更新最远可跳达的位置 
            }
        }
        return true;
    }
};

题目三:45.跳跃游戏II

题目链接

45. 跳跃游戏 II - 力扣(LeetCode)

给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i] 
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]

示例 1:

输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是2.
从下标为 0 跳到下标为 1 的位置,跳 1步,然后跳 3步到达数组的最后一个位置。

示例 2:

输入: nums = [2,3,0,1,4]
输出: 2

提示:

  • 1 <= nums.length <= 104
  • 0 <= nums[i] <= 1000
  • 题目保证可以到达 nums[n-1]
题解:贪心

有了上边那大题的基础,这道题还是不简单。

思路如下:

  1. 初始化变量
    • n:数组 nums 的长度。
    • ans:跳跃次数,初始化为 0。
    • curEnd:当前能够到达的最远位置(下标),初始化为 0(因为从数组的第一个位置开始)。
    • nextEnd:在遍历过程中,记录通过当前位置 i 跳跃能够到达的最远位置(下标),初始化为 0。
  2. 遍历数组
    • 使用一个循环遍历数组 nums 的每个位置 i(从 0 到 n-1)。
    • 在每次循环中,首先更新 nextEnd,即将 nums[i] + i(当前位置 i 加上可以跳跃的最大长度)与 nextEnd 比较,取较大值作为新的 nextEnd。这样,nextEnd 始终保持在当前遍历范围内能够到达的最远位置。
  3. 判断是否需要跳跃
    • 如果当前位置 i 等于 curEnd,说明已经到达了当前能够到达的最远位置,此时需要进行一次跳跃。
    • 将 ans(跳跃次数)加 1,并将 curEnd 更新为 nextEnd,表示通过本次跳跃,能够到达的新的最远位置。
  4. 提前结束循环
    • 如果在遍历过程中发现 nextEnd 已经大于或等于数组的最后一个位置 n-1,说明已经能够到达或超过数组的最后一个位置,此时可以提前结束循环,因为无需再进行更多的跳跃。
  5. 返回结果
    • 循环结束后,返回 ans,即跳跃次数。

代码如下:

class Solution {
public:
    int jump(vector<int>& nums) {
        int n = nums.size();
        if (n <= 1) {         //如果数组长度小于等于1,则不需要跳跃
            return 0;
        }
        int ans = 0;                        //初始化跳跃次数为0
        int curEnd = 0;        //当前能够到达的最远位置(下标) 
        int nextEnd = 0;       //下一步能够到达的最远位置(下标)
        for (int i = 0; i < n; i++) {
            nextEnd = max(nums[i] + i, nextEnd);        //更新下一步能够到达的最远位置(下标)
            if (i == curEnd) {             //如果当前位置i等于当前能够到达的最远位置curEnd,说明需要进行一次跳跃,并更新curEnd为nextEnd 
                ans++;                     
                curEnd = nextEnd;
                if (nextEnd >= n - 1) {     //如果当前已经能够到达或超过数组的最后一个位置,则提前结束循环
                    break; 
                }
            }
        }
        return ans;
    }
};

题目四:1005.K次取反后最大化的数组和 

题目链接

1005. K 次取反后最大化的数组和 - 力扣(LeetCode)

给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:

  • 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。

重复这个过程恰好 k 次。可以多次选择同一个下标 i 。

以这种方式修改数组后,返回数组 可能的最大和 。

示例 1:

输入:nums = [4,2,3], k = 1
输出:5
解释:选择下标 1 ,nums 变为 [4,-2,3] 。

示例 2:

输入:nums = [3,-1,0,2], k = 3
输出:6
解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。

示例 3:

输入:nums = [2,-3,-1,5,-4], k = 2
输出:13
解释:选择下标 (1, 4) ,nums 变为 [2,3,-1,5,4] 。

提示:

  • 1 <= nums.length <= 104
  • -100 <= nums[i] <= 100
  • 1 <= k <= 104
题解:排序+贪心

这道题就好理解多了,我们需要进行k次正负反转,为了尽可能得到最大的结果,我们肯定尽可能优先将最小的负数转化为正数,这就需要先进行排序。

但是我们也得考虑反转次数k比较多的情况,将负数反转完后,如果剩余的k为偶数,那么没有影响,任意对一个正数进行偶次反转还是原来的数;如果剩余的k为奇数,就需要再将数组排序,将最小的值反转为负数即可。

代码如下:

class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        int n = nums.size();
        int ans = 0;
        sort(nums.begin(),nums.end());          //先排序
        for (int i = 0; i < n && k > 0; i++) {
            if (nums[i] < 0) {              //遍历的当前元素为负数且k大于0时,变号-->负数变为正数
                nums[i] = -nums[i];
                k--;
            }
            else {          //其余情况:直接退出循环
                break;
            }
        }
        sort(nums.begin(),nums.end());          //注意:再次排序(因为负数变为正数后大小顺序会发生改变)
        nums[0] = k % 2 == 1 ? -nums[0] : nums[0];      //如果剩余的k是奇数,则将数组中绝对值最小的元素取反。(此时若是偶数,则没影响)
        for (int i = 0; i < n; i++) {
            ans = ans + nums[i];
        }
        return ans;
    }
};

三、小结

今天的打卡到此结束了,贪心的确很难想出来,后边也会继续加油。最后,我是算法小白,但也希望终有所获。

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值