首页 > 其他分享 >123. 买卖股票的最佳时机 III

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

时间:2023-07-04 22:22:24浏览次数:42  
标签:int length 123 最佳时机 数组 prices III dp 利润

1. 题目

读题

 

 

考查点

 

2. 解法

思路

 有两种解法

动态规划

双指针

代码逻辑

 

具体实现

 

动态规划

思路

动态规划的思路是这样的:

  • 我们可以把问题分解成多个子问题,每个子问题都是在某一天结束时,完成了多少次交易,手上是否持有股票,以及此时的最大利润是多少。

  • 我们可以用一个二维数组来表示这些子问题的状态,其中第一维表示天数,第二维表示交易次数和持有状态。例如,dp[i][j][0]表示在第i天结束时,完成了j次交易,手上没有股票,此时的最大利润是多少;dp[i][j][1]表示在第i天结束时,完成了j次交易,手上有股票,此时的最大利润是多少。

  • 我们可以根据题目的限制,确定数组的大小和边界条件。由于我们最多可以完成两次交易,所以第二维的大小是3(0,1,2);由于持有状态只有两种(0,1),所以第三维的大小是2。边界条件是,在第0天结束时,如果没有股票,那么利润为0;如果有股票,那么利润为负无穷(因为还没有买入)。

  • 我们可以根据状态转移方程,递推地计算出每个子问题的答案。状态转移方程是:

    • dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]),表示在第i天结束时,如果没有股票,那么要么是前一天也没有股票,要么是前一天有股票但是今天卖出了,取两者中较大的利润。
    • dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i]),表示在第i天结束时,如果有股票,那么要么是前一天也有股票,要么是前一天没有股票但是今天买入了,取两者中较大的利润。
  • 我们可以从数组的最后一个元素,找出最终的答案。由于我们想要的是最大利润,而且最后一天结束时不持有股票才能获得利润,所以我们只需要比较dp[n-1][0][0], dp[n-1][1][0], dp[n-1][2][0]三者中的最大值即可。

 

 

初始化 dp数线时为什么设置成 Integer.MIN_VALUE

  这是因为我们想要表示一个不可能的状态,即在没有交易的情况下,却持有股票。这样的状态是不合理的,因为我们必须先买入股票才能持有股票,而买入股票就算一次交易。所以我们用一个很小的负数来表示这种状态,这样在比较的时候,它就不会被选中。Integer.MIN_VALUE是JAVA中可以表示的最小的整数,所以我们用它来初始化dp数组。

 

class Solution {
    public int maxProfit(int[] prices) {
        // 如果数组为空或只有一个元素,直接返回0
        if (prices == null || prices.length < 2) {
            return 0;
        }
        // 创建一个三维数组,表示每个子问题的状态
        int[][][] dp = new int[prices.length][3][2];
        // 初始化边界条件
        dp[0][0][0] = 0; // 第0天结束时,没有交易,没有股票,利润为0
        dp[0][0][1] = Integer.MIN_VALUE; // 第0天结束时,没有交易,有股票,利润为负无穷
        dp[0][1][0] = 0; // 第0天结束时,交易了一次,没有股票,利润为0
        dp[0][1][1] = -prices[0]; // 第0天结束时,交易了一次,有股票,利润为负的第一天价格
        dp[0][2][0] = 0; // 第0天结束时,交易了两次,没有股票,利润为0
        dp[0][2][1] = Integer.MIN_VALUE; // 第0天结束时,交易了两次,有股票,利润为负无穷
        // 递推地计算每个子问题的答案
        for (int i = 1; i < prices.length; i++) {
            for (int j = 0; j <= 2; j++) {
                // 如果没有股票,要么是前一天也没有股票,要么是前一天有股票但是今天卖出了
                dp[i][j][0] = Math.max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]);
                // 如果有股票,要么是前一天也有股票,要么是前一天没有股票但是今天买入了
                // 注意j=0时不能买入股票,因为没有交易次数了
                dp[i][j][1] = Math.max(dp[i-1][j][1], j > 0 ? dp[i-1][j-1][0] - prices[i] : Integer.MIN_VALUE);
            }
        }
        // 返回最终的答案,即最后一天结束时不持有股票的最大利润
        return Math.max(dp[prices.length - 1][0][0], Math.max(dp[prices.length - 1][1][0], dp[prices.length - 1][2][0]));
    }
}


双指针算法的思路是这样的:

  • 我们可以把问题分解成两个子问题,即在某个分割点之前和之后分别完成一次交易,然后找出最大的总利润。
  • 我们可以用两个数组来表示这两个子问题的答案,其中一个数组从左到右遍历,记录每一天之前(包括当天)完成一次交易的最大利润;另一个数组从右到左遍历,记录每一天之后(包括当天)完成一次交易的最大利润。
  • 我们可以用两个指针来更新这两个数组,一个指针从左到右,记录最小的买入价格和最大的卖出利润;另一个指针从右到左,记录最大的卖出价格和最大的买入利润。
  • 我们可以遍历所有可能的分割点,即数组的每个下标,将对应的左右子数组的值相加,得到总利润,然后更新最大的总利润。


class Solution {
    public int maxProfit(int[] prices) {
        // 如果数组为空或只有一个元素,直接返回0
        if (prices == null || prices.length < 2) {
            return 0;
        }
        // 创建两个数组,分别存储从左到右和从右到左的最大利润
        int[] left = new int[prices.length]; // left[i]表示在第i天之前(包括第i天)完成一次交易的最大利润
        int[] right = new int[prices.length]; // right[i]表示在第i天之后(包括第i天)完成一次交易的最大利润
        // 初始化左边数组的第一个元素和右边数组的最后一个元素为0
        left[0] = 0;
        right[prices.length - 1] = 0;
        // 初始化左边数组的最小价格和右边数组的最大价格为第一个元素和最后一个元素
        int minPrice = prices[0];
        int maxPrice = prices[prices.length - 1];
        // 从左到右遍历数组,更新左边数组和最小价格
        for (int i = 1; i < prices.length; i++) {
            minPrice = Math.min(minPrice, prices[i]); // 更新最小价格为当前价格和之前的最小价格中较小的一个
            left[i] = Math.max(left[i - 1], prices[i] - minPrice); // 更新左边数组为当前价格减去最小价格和之前的最大利润中较大的一个
        }
        // 从右到左遍历数组,更新右边数组和最大价格
        for (int i = prices.length - 2; i >= 0; i--) {
            maxPrice = Math.max(maxPrice, prices[i]); // 更新最大价格为当前价格和之后的最大价格中较大的一个
            right[i] = Math.max(right[i + 1], maxPrice - prices[i]); // 更新右边数组为最大价格减去当前价格和之后的最大利润中较大的一个
        }
        // 遍历所有可能的分割点,找出最大的总利润
        int maxProfit = 0;
        for (int i = 0; i < prices.length; i++) {
            maxProfit = Math.max(maxProfit, left[i] + right[i]); // 更新最大利润为左边数组和右边数组之和和之前的最大利润中较大的一个
        }
        // 返回最终的结果
        return maxProfit;
    }
}
 

3. 总结

标签:int,length,123,最佳时机,数组,prices,III,dp,利润
From: https://www.cnblogs.com/shoshana-kong/p/17527185.html

相关文章

  • 代码随想录算法训练营第二十一天| 216.组合总和III 17.电话号码的字母组合
    216.组合总和III  思路:很像上一个组合类型的题目,唯一不同的就是自己写一个sum代码:1voidconvertBST_cur(TreeNode*root,vector<TreeNode*>&nodes)2{3if(!root)return;4if(root->left)convertBST_cur(root->left,nodes);5nodes.push_bac......
  • uva 123(排序、检索)
    题目:Searchingandsortingarepartofthetheoryandpracticeofcomputerscience.Forexample,binarysearchprovidesagoodexampleofaneasy-to-understandalgorithmwithsub-linearcomplexity.Quicksortisanefficient[averagecase]comparisonbased......
  • GCM155R71H123JA55D 电容器芯片
    GCM155R71H123JA55D是一种电容器芯片,属于电子元器件中的passives类型。电容器芯片是一种能够存储电荷和电能的元器件,通常使用于电路中起到储存、隔离和滤波作用。GCM155R71H123JA55D电容器芯片的电容值为12nF,电压等级为50V,其特点包括高压容比、无极性、体积小等。它采用陶瓷材料......
  • Authentication to host '10.167.32.123' for user 'root' using method 'mysql_
    连接Mysql5.7以上的版本的数据库出现报错:C#连接远程连接mysql时,抛异常:Authenticationtohost'10.167.32.123'foruser'root'usingmethod'mysql_native_password'failedwithmessage:Readingfromthestreamhasfailed最终在Mysql官网的bug提交区发现已经有人也遇到......
  • FPGA sataII sataIII 固态存储 文件系统FPGA sata2 sata3 固态存储
    FPGAsataIIsataIII固态存储文件系统FPGAsata2sata3固态存储1.支持xilinx全系列FPGA器件2.提供文件系统3.提供硬件解决方案4.移植方便,相当于操作fifo接口就可以了,根据记录行程文件ID:5510000598067161402......
  • CF1239E Turtle
    CF1239E Turtle通过观察我们会发现,第一行一定单调递增,第二行一定单调递减,否则不是最优。再次前提下,乌龟的最优方案只有两种,要么一直向右,最后向下,要么先向下,再一直向右。因此,我们将最小的两个数字放在左上角和右下角,然后把余下数字填入剩余位置,并希望下式最小显然,这是一个背包......
  • Autodesk 123d design官方最新版本下载 软件大全
    软件介绍Autodesk123DDesign是一款完全免费的软件,它能够帮助用户快速而准确地创建3D模型,将模型导出到多种不同格式,为用户节省时间和资源成本,如果你正在寻找一款好用、简单易学但功能齐全的三维CAD软件,那么Autodesk123DDesign肯定是不错的选择。[下载地址]:后台私信我123DDesign......
  • 数列分段 III
    太菜了,只会打暴力。首先考虑二分答案。我们发现,有一个结论:如果每段和都小于\(mid\),那么可行划分的段数构成的集合\(S\)一定是一段区间。接下来证明。严谨证明太难写了,下面是半成品,没写完。说人话,就是归纳。假设前\(i-1\)个前缀满足,说明第\(i\)个前缀也满足。我们把......
  • 图解LeetCode——437. 路径总和 III
    一、题目给定一个二叉树的根节点root ,和一个整数targetSum,求该二叉树里节点值之和等于targetSum的路径的数目。路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。二、示例2.1>示例1:【输入】root=[10,5,-3,3,2,null......
  • python GUI+爬虫——12306抢票软件(1)
    使用python的GUI和爬虫等功能自己构造一个12306的抢票软件。该课程来自网易云课堂的撩课学院,付费课程。地址:网易云课堂搜索以下内容就可找到我跟着学,不一定最后能成功。先试试,想要成功的同学请看我该系列有没有真正实现,如果我没有实现,你可以直接放弃,不用再浪费时间了。简单描述一......