Leetcode习题集-股票交易系列

LeetCode股票交易系列一共有6道题,运用贪心思想和动态规划来解题!


题目

    给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
    设计一个算法来计算你所能获取的最大利润。

条件:在主题干下分别加上以下条件

  • LeetCode 121:最多进行 1 笔交易(k=1)【贪心】
  • LeetCode 122:不限交易次数【二维 DP】
  • LeetCode 309:不限交易次数,但有「冷冻期」的额外条件
  • LeetCode 714:不限交易次数,但有「手续费」的额外条件
  • LeetCode 123:最多进行 2 笔交易(k=2)【三维 DP】
  • LeetCode 188:最多进行 k 次交易

分析

1) 最多进行 1 笔交易(k=1)【贪心算法实现】

思路:循环n天的股票价格;
记录并更新最小价格,已最小价格为买入价;
已当前价格为卖出价,记录并更新最大利润。

方程:参数 i 表示第几天
minPrice = max(price[i],minPrice);
profit = max(price[i]-minPrice,profit);

代码

public int maxProfit1(int[] prices) {
    if (prices.length == 0) {
        return 0;
    }
    int minPrice = prices[0];
    int profit = 0;
    for (int price : prices) {
        minPrice = Math.min(minPrice, price);
        profit = Math.max(profit, price - minPrice);
    }
    return profit;
}

结果在这里插入图片描述

2) 不限交易次数【二维 DP】

思路:其实就是要把每一个上升波段的钱都要赚到。
思路1:找到每次的极小值和极大值,求和每次极大值减去极小值,即可获得最大利润。// 暂不暂时代码。
思路2:二维DP
每天都有三种动作:买入(buy)、卖出(sell)、无操作(rest)。
因为不限制交易次数,因此交易次数这个因素不影响题目,不必考虑。
DP Table 是二维的,两个维度分别是天数(0,1,…,n-1)和是否持有股票(1 表持有,0 表不持有)。

方程
设 i 为第几天,于是可以得到动态规划方程:
p[i][0] = max(p[i - 1][0], p[i - 1][1] + price[i]);
p[i][1] = max(p[i - 1][1], p[i - 1][0] - price[i]);
边界条件为:
p[0][0] = 0; // 第一天不持有股票
p[0][1] = -price[0]; // 第一天买入股票

代码

public int maxProfit2(int[] prices) {
    int n = prices.length;
    if (n == 0) {
        return 0;
    }
    int[][] profit = new int[n][2];
    profit[0][0] = 0;
    profit[0][1] = -prices[0];
    for (int i = 1; i < n; i++) {
        profit[i][0] = Math.max(profit[i - 1][0], profit[i - 1][1] + prices[i]);
        profit[i][1] = Math.max(profit[i - 1][1], profit[i - 1][0] - prices[i]);
    }
    return profit[n - 1][0];
}

结果在这里插入图片描述

3) 不限交易次数,但有「冷冻期」的额外条件。

解释:卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

思路:间隔一天才能买入,假设第 i 天买入,可推算出第 i-1 天和 i-2 天手上都没有股票。

动态规划方程
P[i][0] = max(p[i-1][0],p[i-1][1]+price[i]);
P[i][1] = max(p[i-1][1],p[i-2][0]-prices[i]); // i 天能买股票的条件是 i-2 天或之前手上已经卖出了股票。

代码

public int maxProfit3(int[] prices) {
    int n = prices.length;
    if (n == 0) {
        return 0;
    }
    int[][] profit = new int[n][2];
    profit[0][0] = 0;
    profit[0][1] = -prices[0];
    for (int i = 1; i < n; i++) {
        profit[i][0] = Math.max(profit[i - 1][0], profit[i - 1][1] + prices[i]);
        profit[i][1] = Math.max(profit[i - 1][1], profit[Math.max(i - 2, 0)][0] - prices[i]);
    }
    return profit[n - 1][0];
}

结果在这里插入图片描述

4) 不限交易次数,但有「手续费」的额外条件

解释:每笔交易你只需要为支付一次手续费。

思路:在第二题基础上减去费用就好

动态规划方程
每次买入时减去一个手续费就好
p[i][0] = max(p[i-1][0],p[i-1][1]+price[i]);
p[i][1] = max(p[i-1][1],p[i-1][0]-prices[i]-fee);

代码

public int maxProfit4(int[] prices, int fee) {
    int n = prices.length;
    if (n == 0) {
        return 0;
    }
    int[][] profit = new int[n][2];
    profit[0][0] = 0;
    profit[0][1] = -prices[0] - fee;
    for (int i = 1; i < n; i++) {
        profit[i][0] = Math.max(profit[i - 1][0], profit[i - 1][1] + prices[i]);
        profit[i][1] = Math.max(profit[i - 1][1], profit[i - 1][0] - prices[i] - fee);
    }
    return profit[n - 1][0];
}

结果在这里插入图片描述

5) 最多进行 2 笔交易(k=2)【三维 DP】

思路:多了交易次数的条件,所以升为三维动态规划

动态规划方程
假设到第i天已经进行了j次交易,有:
p[i][j][0] = max(p[i-1][j][0],p[i-1][j][1]+price[i]); // 今天没有进行操作,所以次数依然是j
p[i][j][1] = max(p[i-1][j][1],p[i-1][j-1][0]-price[i]); // 今天进行了操作,上一天操作数就是j-1

边界条件为:
当交易次数 j 为0时
p[i][0][0] = 0;
p[i][0][1] = Integer.MIN_VALUE; // 交易次数为0用最小值表示不可能持有
当天数只有一天时
profit[0][j][0] = 0;
profit[0][j][1] = -prices[0]; // 其实这里的j代表了还剩余多少交易次数,j必须>0才有效

代码

public int maxProfit5(int[] prices) {
    int n = prices.length;
    int k = 2;
    if (n == 0) {
        return 0;
    }
    int[][][] profit = new int[n][k + 1][2];

    // 交易次数为0时
    for (int i = 0; i < n; i++) {
        profit[i][0][0] = 0;
        profit[i][0][1] = Integer.MIN_VALUE;  // 交易次数为0用最小值表示不可能持有
    }
    for (int j = 1; j < k + 1; j++) {
        profit[0][j][0] = 0;
        profit[0][j][1] = -prices[0];
    }
    for (int i = 1; i < n; i++) {
        for (int j = 1; j < k + 1; j++) {
            profit[i][j][0] = Math.max(profit[i - 1][j][0], profit[i - 1][j][1] + prices[i]);
            profit[i][j][1] = Math.max(profit[i - 1][j][1], profit[i - 1][j - 1][0] - prices[i]);
        }
    }
    return profit[n - 1][k][0];
}

结果在这里插入图片描述

6) 最多进行 k 次交易

思路:理论和上题的思路一样,将2换成k就可以了。但是直接提交容易出现超内存的错误,是 DP Table 太大导致的。
在这里插入图片描述
结果果然超出内存限制。
推理可知交易次数最多为n/2次,当K>=n/2时可以按照不限交易次数进行处理,可以转化为二维DP方程,降低了内存

于是得到代码:

public int maxProfit6(int k, int[] prices) {
    int n = prices.length;
    if (n == 0) {
        return 0;
    }

    // 交易次数最多为n/2次,当K>=n/2时按照不限交易次数处理
    if (n / 2 < k) {
        return maxProfit2(prices);  // 调第二题的方法来做
    }

    int[][][] profit = new int[n][k + 1][2];
    for (int j = 1; j < k + 1; j++) {
        profit[0][j][0] = 0;
        profit[0][j][1] = -prices[0];
    }
    // 交易次数为0时
    for (int i = 0; i < n; i++) {
        profit[i][0][0] = 0;
        profit[i][0][1] = Integer.MIN_VALUE;  // 交易次数为0用最小值表示不可能持有
    }
    for (int i = 1; i < n; i++) {
        for (int j = 1; j < k + 1; j++) {
            profit[i][j][0] = Math.max(profit[i - 1][j][0], profit[i - 1][j][1] + prices[i]);
            profit[i][j][1] = Math.max(profit[i - 1][j][1], profit[i - 1][j - 1][0] - prices[i]);
        }
    }
    return profit[n - 1][k][0];
}

结果在这里插入图片描述

总代码和测试样例结果:

将上面六个方法都放到下面这个类中并运行main方法

public class Stock {
    public static void main(String[] args) {
        Stock stock = new Stock();
        int[] prices = new int[]{7, 1, 5, 3, 6, 4};
        System.out.println(stock.maxProfit1(prices));
        System.out.println(stock.maxProfit2(prices));
        System.out.println(stock.maxProfit3(prices));
        prices = new int[]{1, 3, 2, 8, 4, 9};
        System.out.println(stock.maxProfit4(prices, 2));
        prices = new int[]{1, 2, 3, 4, 5};
        System.out.println(stock.maxProfit5(prices));
        prices = new int[]{2, 4, 1};
        System.out.println(stock.maxProfit6(2, prices));
    }
}

测试运行结果:

5
7
5
8
4
2

本文借鉴:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/solution/gu-piao-jiao-yi-xi-lie-cong-tan-xin-dao-dong-tai-g/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值