代码随想录算法训练营第四十九天 | LeetCode 123. 买卖股票的最佳时机 III、188. 买卖股票的最佳时机 IV

简介: 代码随想录算法训练营第四十九天 | LeetCode 123. 买卖股票的最佳时机 III、188. 买卖股票的最佳时机 IV

代码随想录算法训练营第四十九天 | LeetCode 123. 买卖股票的最佳时机 III、188. 买卖股票的最佳时机 IV

文章链接:123. 买卖股票的最佳时机 III188. 买卖股票的最佳时机 IV

视频链接:123. 买卖股票的最佳时机 III188. 买卖股票的最佳时机 IV

1. LeetCode 123. 买卖股票的最佳时机 III

1.1 思路

  1. 121. 买卖股票的最佳时机是在数组中只能买卖一次,122. 买卖股票的最佳时机 II是在数组中可以买卖多次,本题是在数组中至多买卖 2 次,可以 0 次、1 次、2 次
  2. dp 数组及其下标的含义:第 i 天 dp[i][0] 表示不操作,dp[i][1] 表示第 1 次持有,不一定是在第 i 天才买入的,因此是持有,dp[i][2] 表示第 1 次不持有,dp[1][3] 表示第 2 次持有,dp[i][4] 表示第 2 次不持有。以上就是本题的 5 种状态都表示出来了
  3. 递推公式:dp[i][0]=dp[i-1][0],由前一天延续下来;dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i])即延续前一天持有的状态,或者由前一天不操作然后买入变为第 1 次持有,取最大值是因为求的是手头上最多的现金;dp[i][2]=Math.max(dp[i-1][2],dp[i-1][1]+prices[i])即延续前一天不持有的状态,或者由前一天第 1 次持有然后卖出变为第 1 次不持有;dp[i][3]=Math.max(dp[i-1][3],dp[i-1][2]-prices[i])即延续前一天持有的状态,或者由前一天第 1 次不持有然后买入变为第 2 次持有的;dp[i][4]=Math.max(dp[i-1][4],dp[i-1][3]+prices[i])即延续前一天不持有的状态,或者由前一天第 2 次持有然后卖出变为第 2 次不持有的
  4. dp 数组的初始化:从递推公式可以看出都是由前一个状态推导出来的,因此 dp[0][0]=0 因为没操作, dp[0][1]=0-prices[0] 因为第 1 次持有, dp[0][2]=0 因为第 1 次不持有,可以理解为第 0 天买入又在第 0 天卖出了,这是可以的, dp[0][3]=0-prices[i] 因为第 2 次持有,第 1 次的持有与不持有都在同一天买入和卖出了,dp[0][4]=0 因为第 2 次不持有,同理是第 0 天买入和卖出了
  5. 遍历顺序:从递推公式可以看出从前往后 for(int i=1;i<length;i++)从 1 开始是因为第 0 天已经初始化了。最终因为不持有的状态就是卖出了的状态肯定比持有还没卖出的状态有钱,因此是返回 dp[i][2] 和 dp[i][4] 之间取最大值。其实直接返回 dp[length-1][4] 也可以,这里就包括了 dp[length-1][2] 了。你可能疑惑我在第 1 次卖出的时候就是最大值了,其实没关系,如果这个是最大值了,那我再当天买入再卖出也同样是最大值,这就是 dp[length-1][4] 包括了 dp[length-1][2]
  6. 打印 dp 数组:用于 debug

1.2 代码

class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        // 边界判断, 题目中 length >= 1, 所以可省去
        if (prices.length == 0) return 0;
        /*
         * 定义 5 种状态:
         * 0: 没有操作, 1: 第一次买入, 2: 第一次卖出, 3: 第二次买入, 4: 第二次卖出
         */
        int[][] dp = new int[len][5];
        dp[0][1] = -prices[0];
        // 初始化第二次买入的状态是确保 最后结果是最多两次买卖的最大利润
        dp[0][3] = -prices[0];
        for (int i = 1; i < len; i++) {
            dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
            dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
            dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
            dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
        }
        return dp[len - 1][4];
    }
}

2. LeetCode 188. 买卖股票的最佳时机 IV

2.1 思路

  1. 123. 买卖股票的最佳时机 III中我们至多只能买卖两次,本题是至多买卖 k 次。其实两题联系是很紧密的。
  2. dp 数组及其下标的含义:在123. 买卖股票的最佳时机 III中至多买卖 2 次就已经列到了 dp[i][4] 了,因为买卖一次就要有一次买入一次卖出。因此至多买卖 k 次的话每多一次就要多 2 个空位表示状态,因此 dp 数组应该定义成 dp[prices.length][2k+1] 第一维度就是数组大小,第二维度就是 2k+1 这么大,因为下标到 2k+1 这么大。dp 数组的含义和123. 买卖股票的最佳时机 III是一样的,可以看出规律第二维度的数字如果是奇数就是持有,偶数就是不持有。比如 dp[i][1] 就是持有,dp[i][2] 就是不持有
  3. 递推公式:我们需要一个 for循环才能把这些状态都表示出来。第二维度的下标都用 j 表示 for(int i=0;i<prices.length;i++)for(int j=0;j<2k;j+=2)持有状态:dp[i][j+1]=Math.max(dp[i-1][j+1],dp[i-1][j]-prices[i])这里即从前一天持有的状态延续,或者由前一天不持有的状态买入变为持有;不持有状态:dp[i][j+2]=Math.max(dp[i-1][j+2],dp[i-1][j+1]+prices[i])这里即从前一天不持有的状态延续,或者由前一天持有的状态卖出变为不持有。j 这层 for循环后面 j<2k 是因为我们在下面进行了加一,比如 k 等于 2,那 dp[i][1] 和 dp[i][2] 都是 k=1 时的状态,dp[i][3] 和 dp[i][4] 都是 k=2 时的状态
  4. dp 数组的初始化:根据递推公式初始化第 0 天的状态,第二维度的下标的状态是在只有奇数的情况才会 0-prices[i],都表示为第几次持有,就通过 for(int j=1;j<2k;j+=2)dp[0][j]=0-prices[i],这里 j 从 1 开始是因为 dp[0][1] 是第 1 次买入持有,从这里开始,为什么 j<2k,同样代入个数字,k=2 时,j=3<2*2 同样可以把 dp[0][3] 这个奇数的情况初始化
  5. 遍历顺序:从前往后遍历
  6. 打印 dp 数组:用于 debug

2.2 代码

// 版本二: 二维 dp数组
class Solution {
    public int maxProfit(int k, int[] prices) {
        if (prices.length == 0) return 0;
        // [天数][股票状态]
        // 股票状态: 奇数表示第 k 次交易持有/买入, 偶数表示第 k 次交易不持有/卖出, 0 表示没有操作
        int len = prices.length;
        int[][] dp = new int[len][k*2 + 1];
        // dp数组的初始化, 与版本一同理
        for (int i = 1; i < k*2; i += 2) {
            dp[0][i] = -prices[0];
        }
        for (int i = 1; i < len; i++) {
            for (int j = 0; j < k*2 - 1; j += 2) {
                dp[i][j + 1] = Math.max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
                dp[i][j + 2] = Math.max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
            }
        }
        return dp[len - 1][k*2];
    }
}


相关文章
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
13天前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
1月前
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
26 2
|
3月前
|
算法
leetcode188 买卖股票的最佳时机IV
leetcode188 买卖股票的最佳时机IV
62 0
|
28天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
5天前
|
算法 数据安全/隐私保护 索引
OFDM系统PAPR算法的MATLAB仿真,对比SLM,PTS以及CAF,对比不同傅里叶变换长度
本项目展示了在MATLAB 2022a环境下,通过选择映射(SLM)与相位截断星座图(PTS)技术有效降低OFDM系统中PAPR的算法实现。包括无水印的算法运行效果预览、核心程序及详尽的中文注释,附带操作步骤视频,适合研究与教学使用。
|
13天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。
|
14天前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。
|
15天前
|
存储 算法 决策智能
基于免疫算法的TSP问题求解matlab仿真
旅行商问题(TSP)是一个经典的组合优化问题,目标是寻找经过每个城市恰好一次并返回起点的最短回路。本文介绍了一种基于免疫算法(IA)的解决方案,该算法模拟生物免疫系统的运作机制,通过克隆选择、变异和免疫记忆等步骤,有效解决了TSP问题。程序使用MATLAB 2022a版本运行,展示了良好的优化效果。
|
14天前
|
机器学习/深度学习 算法 芯片
基于GSP工具箱的NILM算法matlab仿真
基于GSP工具箱的NILM算法Matlab仿真,利用图信号处理技术解析家庭或建筑内各电器的独立功耗。GSPBox通过图的节点、边和权重矩阵表示电气系统,实现对未知数据的有效分类。系统使用MATLAB2022a版本,通过滤波或分解技术从全局能耗信号中提取子设备的功耗信息。
下一篇
无影云桌面