代码随想录day28,贪心算法part2

122.买卖股票的最佳时机II

题目

力扣题目链接

给你一个整数数组 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 。

思路

本题首先要清楚两点:

  • 只有一只股票!

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

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

把利润分解为每天为单位的维度,而不是整体去考虑!

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

收集正利润的区间,就是股票买卖的区间,而我们只需要关注最终利润,不需要记录区间

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

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

看着难,只要写出每天利润的数组就不难了,代码很短

代码

java 复制代码
class Solution{
    public int maxProfit(int[] prices){
        int result=0;
        for(int i =1;i<prices.length;i++){
            result+=Math.max(prices[i]-prices[i-1],0);
        }
        return result;
    }
}

55. 跳跃游戏

题目

力扣题目链接

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

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

示例 1:

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

思路

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

其实跳几步无所谓,关键在于可跳的覆盖范围!不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围。这个范围内,别管是怎么跳的,反正一定可以跳过来。

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

贪心求法就是想要局部最优,比如说一次跳跃,我只想要最远的,如果我最大能跳三步,那走到最远的就是跳三步,不用去想跳一步和两步后序的结果,我每次只要最大的。这就是贪心选择性质,通过局部最优解就能推导全局最优解,不需要等到递归计算出所有子问题的答案才能做选择。

代码

java 复制代码
class Solution{
    public boolean canJump(int[] nums){
        int n = nums.length;
        int farthest = 0;
        //遍历数组的前n-1 个元素,避免处理最后一个元素
        for (int i = 0;i<n-1;i++){
            //不断计算能跳的最远距离
            farthest = Math.max(farthest,i+nums[i]);
            //可能碰到0,跳不了了
            if(farthest<=i){
                return false;
            }
        }
//当前能到的最远位置大于或等于最后一个索引(n-1),就说明能跳到终点
        return farthest >=n-1;
    }
}

输入:[2,3,1,1,4] 输出:true

思路:

  • 站在位置i,最远能跳到 i + nums[i]

  • 维护一个全局最远可达 maxReach

  • 如果 maxReach >= 最后位置,就能到

过程: i=0: nums[0]=2, maxReach = max(0, 0+2) = 2 i=1: i=1 <= maxReach=2 ✓, nums[1]=3, maxReach = max(2, 1+3) = 4 4 >= 4(最后位置),返回true!

45.跳跃游戏II

题目

力扣题目链接

给定一个长度为 n0 索引 整数数组 nums。初始位置在下标 0。

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

  • 0 <= j <= nums[i]

  • i + j < n

返回到达 n - 1 的最小跳跃次数。测试用例保证可以到达 n - 1

示例 1:

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

思路

现在的问题是,保证你一定可以跳到最后一格,请问你最少要跳多少次,才能跳过去。

如果使用贪心算法,需要思考贪心选择性质,是否能够通过局部最优解推导全局最优解,避免全量穷举所有的可能解。我们不需要真的递归穷举出所有选择的具体结果来比较求最值,而只需要每次选择那个最有潜力的局部最优解,最终就能得到全局最优解。

比如我可以往前跳2步,跳一步到4,那4能继续跳4步;跳两步到1,1只可以继续跳1步;所以我不是选择当前跳的最远的,而是考虑两步整体跳到最远的

维度 55. 跳跃游戏 45. 跳跃游戏 II
问题 能不能跳到最后? 最少几步跳到最后?
答案类型 boolean(true/false) int(最小步数)
贪心策略 维护最远可达距离 维护当前步数内最远可达
遍历方式 一次遍历,看能否覆盖终点 分层遍历,每步看最远能到哪

输入:[2,3,1,1,4] 输出:2

解释:从索引 0 跳 1 步到索引 1,然后再跳 3 步到最后。

思路:

  • 每一步(jump)能到达的范围是一个"区间"

  • 在这个区间内,找下一步能跳到的最远位置

  • 当走到当前区间的尽头,必须再跳一步

过程: 初始:step=0, 当前区间[0,0](只有起点)

step=0的区间[0,0]:

  • i=0, nums[0]=2, 下一步最远到 0+2=2

  • 当前区间结束,step++=1, 下一区间[1,2]

step=1的区间[1,2]:

  • i=1, nums[1]=3, 最远到 1+3=4

  • i=2, nums[2]=1, 最远到 2+1=3

  • 当前区间结束,step++=2, 下一区间[3,4]

step=2的区间[3,4]:

  • i=3, nums[3]=1, 最远到 4

  • i=4, 到达终点!

  • 但已经在终点,不需要再step++

结果:2步

代码

java 复制代码
class Solution {
    public int jump(int[] nums) {
        int n = nums.length;
        if (n == 1) return 0;
        
        int steps = 0;      // 已用步数
        int curEnd = 0; //当前步数能到达的最远位置(区间右边界)
        int maxReach = 0; //下一步能到达的最远位置
        
        for (int i = 0; i < n - 1; i++) {  // 注意:不到最后一个元素
            // 在当前步数范围内,找下一步最远能到哪
            maxReach = Math.max(maxReach, i + nums[i]);
            
            //到达当前区间的尽头,必须再跳一步
            if (i == curEnd) {
                steps++;
                //更新下一区间的右边界
                curEnd = maxReach; 
                
                // 提前结束
                if (curEnd >= n - 1) break;
            }
        }        
        return steps;
    }
}

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

题目

力扣题目链接

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

选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。重复这个过程恰好 k 次。可以多次选择同一个下标 i 。以这种方式修改数组后,返回数组 可能的最大和

示例 1:

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

示例 2:

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

思路

如何可以让数组和最大呢?

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

局部最优可以推出全局最优。

那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。

那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。

那么本题的解题步骤为:

  • 第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小

  • 第二步:从前向后遍历,遇到负数将其变为正数,同时K--

  • 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完

  • 第四步:求和

代码

java 复制代码
class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        if (nums.length == 1) return nums[0];

        // 排序:先把负数处理了
        Arrays.sort(nums); 

        for (int i = 0; i < nums.length && k > 0; i++) { // 贪心点, 通过负转正, 消耗尽可能多的k
            if (nums[i] < 0) {
                nums[i] = -nums[i];
                k--;
            }
        }

        //退出循环, k > 0||k<0(k消耗完了不用讨论)
        if (k % 2 == 1) { // k > 0 && k is odd:对于负数:负-正-负-正
            Arrays.sort(nums); // 再次排序得到剩余的负数,或者最小的正数
            nums[0] = -nums[0];
        }
        // k > 0 && k is even,flip数字不会产生影响: 对于负数: 负-正-负;对于正数:正-负-正 

        int sum = 0;
        for (int num : nums) { // 计算最大和
            sum += num;
        }
        return sum;
    }
}
相关推荐
寻寻觅觅☆6 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
偷吃的耗子7 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
青云计划7 小时前
知光项目知文发布模块
java·后端·spring·mybatis
赶路人儿7 小时前
Jsoniter(java版本)使用介绍
java·开发语言
化学在逃硬闯CS8 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar1238 小时前
C++使用format
开发语言·c++·算法
探路者继续奋斗8 小时前
IDD意图驱动开发之意图规格说明书
java·规格说明书·开发规范·意图驱动开发·idd
Gofarlic_OMS8 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
夏鹏今天学习了吗9 小时前
【LeetCode热题100(100/100)】数据流的中位数
算法·leetcode·职场和发展
消失的旧时光-19439 小时前
第十九课:为什么要引入消息队列?——异步系统设计思想
java·开发语言