股票买卖问题全家桶(已团灭)

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。股票买卖问题全家桶(已团灭),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

动态规划 —— LeetCode”股票买卖问题” 全家桶

本文主要是根据leetcode题解视频整理的一套解决 "股票买卖问题"的思路和代码,一起来看看吧!
本文会先根据这些股票问题整理出通用的 “状态转移方程”,然后针对每个问题作出相应的改变。
题目链接已贴出,输入输出示例等题目详情请看 原题链接!
代码部分(java)是我自己整理的,供大家参考!
(过OI的话直接copy对应方法体即可)



“股票买卖问题”总体分析

限制条件

  • 先买入才能卖出
  • 不能同时参加多笔交易,再次买入时,需要先卖出
  • k >= 1才能进行交易,否则没有交易次数

定义操作

  • 买入(buy)
  • 卖出(sell)
  • 不操作(沿用上一次的数据)

定义状态

  • i:天数
  • k:交易次数,每次交易包含买入和卖出,这里我们只在买入的时候需要将k – 1(这里解释一下:因为题目规定如果手中有股票是不能继续买入的,所以买入和卖出是一组连续的操作,我们只需要在其中一处-1即可。)
  • 0:不持有股票
  • 1:持有股票

举例

举例
dp[i][k][0]//第i天还可以交易k次手中没有股票
dp[i][k][1]//第i天还可以交易k次手中有股票

最终的最大收益是dp[n - 1][k][0]而不是dp[n - 1][k][1],
因为最后一天卖出肯定比持有收益更高

状态转移方程

//今天没有持有股票,分为两种情况:
// 1. dp[i - 1][k][0],昨天没有持有,今天不操作。
// 2. dp[i - 1][k][1] + prices[i]昨天持有,今天卖出,今天手中就没有股票了。
dp[i][k][0] = Math.max( dp[i - 1][k][0],dp[i - 1][k][1] + prices[i])

//今天持有股票,分为两种情况:
// 1.dp [i - 1][k][1]昨天持有,今天不操作
// 2.dp[i - 1][k - 1][0] - prices [i]昨天没有持有,今天买入。
dp[i][k][1] = Math.max(dp[i - 1][k][1],dp[i - 1][k - 1][0] - prices[i])
//最大利润就是这俩种情况的最大值

下面这张图可以帮助理解状态转移方程的由来
在这里插入图片描述

题目

121. 买卖股票的最佳时机(easy)

原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/

题目:
给定一个数组 prices ,它的第i 个元素prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

分析

//第i天不持有由第i-1天不持有然后不操作和第i-1天持有然后卖出两种情况的最大值转移过来
 dp[i][1][0] = Math.max(dp[i - 1][1][0], dp[i - 1][1][1] + prices[i])
//第i天持有由第i-1天持有然后不操作和第i-1天不持有然后买入两种情况的最大值转移过来
 dp[i][1][1] = Math.max( dp[i - 1][1][1],dp[i - 1][0][0] - prices[i])
//           = Math.max(dp[i-1][1][1],-prices[i])// k=0时没有交易次数,dp[i-1][0][0] = 0

k是固定值1,不影响结果,所以可以不用管,简化之后如下
dp[i][0] = Math.max(dp[i - 1][0],dp[i - 1][1] + prices[i])
dp[i][1] = Math.max ( dp[i - 1][1],-prices [i])

code

public class dp_买卖股票的最佳时机121 {
    public static void main(String[] args) {
        int[] num = {7, 1, 5, 3, 6, 4};
//        int[] num = {7, 6, 4, 3, 1};
        System.out.println(maxProfit(num));
    }

    /*//完整代码
    public static int maxProfit(int[] prices) {
        int length = prices.length;
        int[][] dp = new int[length][2];
        dp[0][0] = 0;//第0天不持有
        dp[0][1] = -prices[0];//第0天持有
        for (int i = 1; i < length; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
        }
        return dp[length - 1][0];
    }*/

    /*//状态压缩,利用滚动数组降维
    public static int maxProfit(int[] prices) {
        int length = prices.length;
        int[] dp = new int[2];
        dp[0] = 0;//第0天不持有
        dp[0] = -prices[0];//第0天持有
        for (int i = 1; i < length; i++) {
            dp[0] = Math.max(dp[0], dp[1] + prices[i]);
            dp[1] = Math.max(dp[1], -prices[i]);
        }
        return dp[0];
    }*/


    //语义化
    public static int maxProfit(int[] prices) {
        int sell = 0;//出售
        int buy = -prices[0];//买入
        for (int i = 1; i < prices.length; i++) {
            sell = Math.max(sell, buy + prices[i]);
            buy = Math.max(buy, -prices[i]);
        }
        return sell;
    }
}

这道题还有一个我自己实现的版本,
思路是:前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}

public class dp_买卖股票的最佳时机121_self {
    //        动态规划 前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}
    public static void main(String[] args) {
        int[] num = {7, 1, 5, 3, 6, 4};
//        int[] num = {7, 6, 4, 3, 1};
        System.out.println(maxProfit(num));
    }

    public static int maxProfit(int[] prices) {
        int[] dp = new int[prices.length];
        int min = prices[0];
        for (int i = 1; i < prices.length; i++) {
            dp[i] = Math.max(dp[i - 1], prices[i] - min);
            min = Math.min(min, prices[i]);
        }
        return dp[dp.length - 1];
    }
}

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

原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/

题目:
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润。

分析

//第i天不持有由第i-1天不持有然后不操作和第i-1天持有然后卖出两种情况的最大值转移过来
dp[i][k][0] = Math.max(dp[i - 1][k][0],dp[i - 1][k][1] + prices[i])
//第i天持有由第i-1天持有然后不操作和第i-1天不持有然后买入两种情况的最大值转移过来
dp[i][k][1] = Math.max(dp[i - 1][k][1],dp[i - 1][k - 1][0] - prices[i])

k同样不影响结果,简化之后如下
dp[i][0] = Math.max( dp[i - 1][0], dp[i - 1][1] + prices[i])
dp[i][1] = Math.max( dp[i - 1][1], dp[i - 1][0] - prices[i])

code

public class dp_买卖股票的最佳时机122 {
    public static void main(String[] args) {
//        int[] num = {7, 1, 5, 3, 6, 4};
        int[] num = {1, 2, 3, 4, 5};
        System.out.println(maxProfit(num));
    }

    //    完整代码
    /*public static int maxProfit(int[] prices) {
        int length = prices.length;
        int[][] dp = new int[length][2];
        dp[0][0] = 0;//第0天不持有
        dp[0][1] = -prices[0];//第0天买入 花了prices[0]元
        for (int i = 1; i < length; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
        }
        return dp[length - 1][0];
    }*/

    //  状态压缩,利用滚动数组去掉一维
    /*public static int maxProfit(int[] prices) {
        int length = prices.length;
        int[] dp = new int[2];
        dp[0] = 0;
        dp[1] = -prices[0];
        for (int i = 1; i < length; i++) {
            dp[0] = Math.max(dp[0], dp[1] + prices[i]);
            dp[1] = Math.max(dp[1], dp[0] - prices[i]);
        }
        return dp[0];
    }*/

    //语义化
    public static int maxProfit(int[] prices) {
        int sell = 0;
        int buy = -prices[0];
        for (int i = 1; i < prices.length; i++) {
            sell = Math.max(sell, buy + prices[i]);
            buy = Math.max(buy, sell - prices[i]);
        }
        return sell;
    }
}

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

原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/

题目:
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

分析

状态转移方程
dp[i][k][0] = Math.max( dp[i - 1][k][0],dp[i - 1][k][1] + prices[i])
dp[i][k][1] = Math.max(dp[i - 1][k][1],dp[i - 1][k - 1][0] - prices[i])

k对结果有影响不能舍取,只能对k进行循环
for ( let i = 0; i < n; i++) {
    for (let k = maxK; k >= 1; k--) {
        dp[i][k][0] = Math.max( dp[i - 1][k][0],dp[i - 1][k][1] + prices[i] );
        dp[i][k][1] = Math.max(dp[i - 1][k][1],dp[i - 1][k - 1][0] - prices[i] );
    }
}

// k=2,由于k很小,这里直接写出循环的结果来进一步优化
dp[i][2][0] = Math.max(dp[i - 1][2][0],dp[i - 1][2][1] + prices[i])
dp[i][2][1] = Math.max(dp[i - 1][2][1], dp[i - 1][1][0] - prices[i])

dp[i][1][0] = Math.max(dp[i - 1][1][0],dp[i - 1][1][1] + prices[i])
dp[i][1][1] = Math.max( dp[i - 1][1][1],dp[i - 1][0][0] - prices[i])
            = Math.max(dp[i - 1][1][1],-prices[i])// k=0时没有交易次数,dp[i - 1][0][0] = 0

//去掉i这一维度
dp[2][0] = Math.max( dp[2][0], dp[2][1] + prices[i])
dp[2][1] = Math.max(dp [2] [1], dp[1][0] - prices[i])

dp[1][0] = Math.max( dp[1][0], dp[1][1] + prices [i])
dp[1][1] = Math.max( dp[1][1], dp[0][0] - prices [i] )
         = Math.max(dp[1][1],-prices[i])// k=0时没有交易次数,dp[i - 1][0][0] = 0

code

public class dp_买卖股票的最佳时机123 {
    public static void main(String[] args) {
        int[] num = {3, 3, 5, 0, 0, 3, 1, 4};
//        int[] num = {1, 2, 3, 4, 5};
        System.out.println(maxProfit(num));
    }

    //完整代码 + 降维 + 语义化
    public static int maxProfit(int[] prices) {
        int length = prices.length;
        int sell1 = 0, sell2 = 0;
        int buy1 = -prices[0], buy2 = -prices[0];

        for (int i = 1; i < length; i++) {
            sell2 = Math.max(sell2, buy2 + prices[i]);
            buy2 = Math.max(buy2, sell1 - prices[i]);
            sell1 = Math.max(sell1, buy1 + prices[i]);
            buy1 = Math.max(buy1, -prices[i]);
        }
        return sell2;
    }

}

188. 买卖股票的最佳时机 IV(hard)

原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/

题目:
给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

分析

总体情况和122题类似,
122题可以交易无数次,188交易k次,所以直接在加一层k循环就可以
122最后的递推方程:
sell = Math.max( sell,buy + prices [i] );
buy = Math.max ( buy, -prices[i] );

code

public class dp_买卖股票的最佳时机188 {
    public static void main(String[] args) {
        int[] num = {2, 4, 1};
//        int[] num = {3,2,6,5,0,3};
        System.out.println(maxProfit(2, num));
    }

    //完整代码 + 降维 + 语义化
    public static int maxProfit(int k, int[] prices) {
        int n = prices.length;
        int[][] dp = new int[k + 1][2];//和123题—样求出所有k的状态
        //初始化k次交易买入卖出的利润
        for (int i = 0; i <= k; i++) {
            dp[i][0] = -prices[0];//buy  表示有股票
            dp[i][1] = 0;//sell 表示没有股票
        }
        for (int price : prices) {
            for (int j = 1; j <= k; j++) {
                //122题可以交易无数次,188交易k次,所以直接在加一层k循环就可以
                //122最后的递推方程:
                //sell = Math.max( sell,buy + prices [i] );
                //buy = Math.max ( buy, -prices[i] );
                dp[j][1] = Math.max(dp[j][1], dp[j][0] + price);
                dp[j][0] = Math.max(dp[j][0], dp[j - 1][1] - price);
            }
        }
        return dp[k][1];//返回第k次清空手中的股票之后的最大利润

    }

}

309. 买卖股票的最佳时机含冷冻期(medium)

原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/

题目:
给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

分析

状态转移方程
dp[i][k][0] = Math.max( dp[i - 1][k][0],dp[i - 1][k][1] + prices[i])
//冷却时间1天,所以要从i - 2天转移状态
//买入,卖出—---冷冻期—---买入,卖出
dp[i][k][1] = Math.max(dp[i - 1][k][1],dp[i - 2][k - 1][0] - prices[i])

题目不限制k的大小,可以舍去
dp[i][0] = Math.max( dp[i - 1][0], dp[i - 1][1] + prices[i])
dp[i][1] = Math.max( dp[i - 1][1], dp[i - 2][0] - prices[i])
//降维i
dp[0] = Math.max(dp[0], dp[1] + prices [i])
dp[1] = Math.max (dp[1], profit_freeze - prices [i])

code

public class dp_买卖股票的最佳时机含冷冻期309 {
    public static void main(String[] args) {
//        int[] num = {1, 2, 3, 0, 2};//3
        int[] num = {1};//0
        System.out.println(maxProfit(num));
    }

    //完整代码 + 降维 + 语义化
    public static int maxProfit(int[] prices) {
        int n = prices.length;
        int buy = -prices[0];//手中有股票
        int sell = 0;//没有股票
        int profit_freeze = 0;
        for (int i = 1; i < n; i++) {
            int temp = sell;
            sell = Math.max(sell, buy + prices[i]);
            buy = Math.max(buy, profit_freeze - prices[i]);
            profit_freeze = temp;
        }
        return sell;
    }
}

714. 买卖股票的最佳时机含手续费(medium)

原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/

题目:
给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

分析

状态转移方程
//每次交易要支付手续费我们定义在卖出的时候扣手续费
dp[i][0] = max(dp[i - 1][0],dp[i - 1][1] + prices [i] - fee)
dp[i] [1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])

code

public class dp_买卖股票的最佳时机含手续费714 {
    public static void main(String[] args) {
        int fee = 2;
//        int fee = 3;
        int[] num = {1, 3, 2, 8, 4, 9};//8
//        int[] num = {1,3,7,5,10,3};//6
        System.out.println(maxProfit(num, fee));
    }

    //完整代码 + 降维 + 语义化
    public static int maxProfit(int[] prices, int fee) {
        int sell = 0;
        int buy = -prices[0];
        for (int price : prices) {
            sell = Math.max(sell, buy + price - fee);
            buy = Math.max(buy, sell - price);
        }
        return sell;
    }
}

总结

"股票买卖问题"是动态规划的一种,核心思想还是那句话:“当前的结果都是由之前的过程推导出来的” ,需要注意的是,股票问题属于一类问题,我们应对其进行一个总体的思考,根据限制条件和相应操作来定义状态,本题需要定义三个状态,但是具体到每道题的话,我们可以将不需要的状态去掉,并且可以通过滚动数组降维,从而进行状态压缩,最后通过适当的语义化来增强可读性。

参考leetcode中的视频讲解:一种解法团灭买卖股票问题

文章粗浅,希望对大家有帮助!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/159820.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!