• 代码随想录32——贪心:122买卖股票的最佳时机II、55跳跃游戏、45跳跃游戏II


    1.122买卖股票的最佳时机II

    1.1.题目

    在这里插入图片描述

    1.2.解答

    1.2.1.思路

    本题首先要清楚两点:

    • 只有一只股票!
    • 当前只有买股票或者卖股票的操作

    想获得利润至少要两天为一个交易单元

    这道题目可能我们只会想,选一个低的买入,再选个高的卖,再选一个低的买入…循环反复。其实这样操作是比较复杂的。

    如果想到其实最终利润是可以分解的,那么本题就很容易了

    如何分解呢?

    假如第0天买入,第3天卖出,那么利润为:prices[3] - prices[0]。

    相当于(prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])。

    此时就是把利润分解为每天为单位的维度,而不是从0天到第3天整体去考虑!

    那么根据prices可以得到每天的利润序列:(prices[i] - prices[i - 1])…(prices[1] - prices[0])。

    如图:

    在这里插入图片描述
    一些同学陷入:第一天怎么就没有利润呢,第一天到底算不算的困惑中。

    第一天当然没有利润,至少要第二天才会有利润,所以利润的序列比股票序列少一天

    从图中可以发现,其实我们需要收集每天的正利润就可以,收集正利润的区间,就是股票买卖的区间,而我们只需要关注最终利润,不需要记录区间

    那么只收集正利润就是贪心所贪的地方!

    局部最优:收集每天的正利润,全局最优:求得最大利润

    1.2.2.自己理解

    其实看上面的图就会发现,这道题和前面的摆动序列的题目是一样的!如果用摆动序列那道题目的画山峰的方法来思考,其实这道题目就是在收集所有的上坡的高度!例如:

    • 如果有一个连续的上坡,那么在这个区间中我们就可以一直累加相邻两个点构成的小区间的差值,直到把所有小区间都累加完毕。
    • 然后对单个的上坡,那么就相当于一个小区间,直接把他加到最终的结果中即可。

    所以最后代码非常简单:只要是当前小区间是上坡,就累加差值即可。连续的上坡中包含多个小区间,累加它其中的差值就是把整个连续的上坡的差值算出来了;然后单个的小上坡累加差值,相当于把他累加到了最后的结果中。

    一下代码是自己写的,非常简单,实测也可以AC。

    int maxProfit(vector<int> &prices)
    {
        int pre = 0;
        int cur = 1;
        int sum = 0;
        while(cur < prices.size())
        {
            int diff = prices[cur++] - prices[pre++];
            if(diff > 0)
                sum += diff;
        }
    
        return sum;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.55跳跃游戏

    参考:代码随想录,55跳跃游戏

    2.1.题目

    在这里插入图片描述

    2.2.解答

    这道题目还挺有意思的

    刚看到本题一开始可能想:当前位置元素如果是3,我究竟是跳一步呢,还是两步呢,还是三步呢,究竟跳几步才是最优呢?

    其实跳几步无所谓,关键在于可跳的覆盖范围!

    不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围

    这个范围内,别管是怎么跳的,反正一定可以跳过来。

    那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点!

    每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围

    在这里插入图片描述

    注意不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围。然后for循环就在这个范围内每次跳一步,每次跳到一个新的位置,都计算从这个位置开始的最大跳跃范围;如果这个范围比之前的覆盖范围大了,说明我们这次跳跃是有进步的,所以我们就把最大范围更新,然后仍然继续往下跳。在这个过程中,最大范围有可能一直变大,也有可能调到某个位置的时候达到了最大。不管怎么样,只要在这个过程中有一次最大范围覆盖了数组的长度,那么就可以直接返回了。如果说我们遍历完了我们的最大范围,都没有超过数组终点,说明不能调到终点。

    所以这道题目感觉还有点动态规划的味道,每跳一次,都会更新for循环的范围,也就是我们能够跳的最大范围。代码如下,可以看到for循环的终止条件是cover,而在for循环中一直在修改cover,所以有种动态规划的感觉。

    bool canJump(vector<int> &nums)
    {
        if(nums.size() == 1)
            return true;
        
        int cover = 0;
        for(int i = 0; i <= cover; i++)
        {
            // 跟新新的最大范围
            cover = cover < (i + nums[i]) ? (i + nums[i]) : cover;
            if(cover >= nums.size() - 1)  // 一旦可以达到最大范围,直接return
                return true;
        }   
    
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.45跳跃游戏II

    参考:代码随想录,45跳跃游戏II

    3.1.题目

    在这里插入图片描述

    3.2.解答

    3.2.1.思路

    注意:这道题在代码随想录网站上写的挺复杂的,看了代码可以理解思路,但是感觉写的太复杂。最后自己写的代码见后面,感觉更简单。

    这道题目本质上考察的还是每次跳的范围的循环,本质上和上面那道题目是一样的。但是上面的题目写的比较简单,所以省略了细节,而本体把更新最大范围的细节暴露出来了。

    思路仍然是更新最大范围,但是这里是不像上面只要发现当前位置的范围比已有的最大范围大了就更新,而是遍历完当前范围内的所有位置,然后去寻找下一次遍历的最大范围。只要遍历完当前范围的所有位置还没到终点,说明我们必须要再跳一步,到下一个范围里继续遍历了,此时就把步数+1即可。一旦找到一个范围可以到达终点,那么就立即返回,此时就是最小的步数。

    代码如下,注意其中的注释,基本思想和上面那道题是一样的,仍然是寻找最大范围:

    int jump(vector<int> &nums)
    {
        // 判断特殊情况:如果只有一个数,那么直接就是在终点了,不用跳
        if (nums.size() == 1)
            return 0;
    
        int ans = 0;   // 最终跳的步数
        int this_cover = nums[0];   // 开始位置覆盖的范围
        int next_cover = 0;   // 如果跳到当前覆盖范围内的所有位置,可以覆盖到的最大的新的范围
    
        // 先判断从初始位置一步能不能跳到最终位置,如果能则直接返回结果
        if(this_cover >= nums.size() - 1)   
            return 1;
        
        // 运行到这里,说明要多跳几步
        int i = 0;   // 注意 i 要定义在for循环外面,保证切换最大范围遍历时候,是从当前位置继续向后遍历
        for(; i <= this_cover; i++)
        {
            // 遍历当前范围内的所有位置,寻找下一步的最大范围
            next_cover = max(next_cover, i + nums[i]);  // 求下一步的最大范围
            
            // 遍历到当前范围内的最后一个位置,那么上面也把下一步的最大范围next_cover统计出来了
            //  因此更新遍历的this_cover,用于下一次的继续遍历
            if(i == this_cover)  
            {
                ans++;  // 要新跳一步了
                this_cover = next_cover;   // 下次遍历的最大范围更新
                // 如果下次遍历的最大范围覆盖到数组末尾了,那么不用再遍历了,已经找到最小步数了
                if(this_cover >= nums.size() - 1)
                {
                    ans++;  // 在下一个范围中跳一步,就直接到终点了
                    return ans;   // 返回最终结果
                }
            }
        }
    
        // 因为本题说一定可以调到终点,所以实际不会运行到这里
        //  这里写return 只是为了满足函数有返回值的要求
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    3.2.2.统一55题和45题代码思路

    55题中,是每到一个位置都更新最大范围,而没有清晰的分出来每一次的最大范围。虽然代码上更加简单,但是导致到本题中使用原方法就很难写出来了。所以这里把55题修改一下,变成每次清晰的分出最大范围的写法,这样两道题可以使用同样的思想。

    代码如下所示,经过测试可以AC。

    bool canJump(vector<int> &nums)
    {
        if(nums.size() == 1)   // 只有一个数,不用跳就在终点,所以是true
            return true;
        
        int this_cover = nums[0];  // 当前遍历的最大范围
        int next_cover = 0;   // 从当前遍历的范围中计算下一次遍历的最大范围
        
        if(this_cover >= nums.size() - 1)  // 第一步就能跳到最终位置
            return true;
        
        // 运行到这里说明第一步调不到终点,还要多跳几步
        int i = 0;  // 注意 i 要定义在for循环外面,保证切换最大范围遍历时候,是从当前位置继续向后遍历
        for(; i <= this_cover; i++)    // 遍历当前的范围,寻找下一次的最大范围
        {
            next_cover = max(next_cover, i + nums[i]);
            if(i == this_cover)   // 遍历到当前最后一个位置了
            {
                this_cover = next_cover;   // 更新下一次遍历的最大范围
                if(this_cover >= nums.size() - 1)  // 一旦可以达到最大范围,直接return
                    return true;
            }     
        }   
    
        // 运行到这里,说明无法跳到终点
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
  • 相关阅读:
    nodeJs--fs模块
    vue3+elementPlus:el-table-column表格列动态设置单元格颜色
    【uniapp】真机运行 访问电脑本地接口127.0.0.1网络失败(亲测有用!!)
    国内外智能家居不同的发展路线比较
    3_docker部署mysql主主备份
    【RabbitMQ】——消息应答
    C++内存管理:其六、静态allocator的实现
    HTML5笔记
    LVGL介绍
    “网站不安全”该如何解决
  • 原文地址:https://blog.csdn.net/qq_42731705/article/details/127454117