股票交易

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
题解 1 (超内存限制)
//官方提示的是用动态规划,最开始想得到是dp[i][j]  表示我们从i到j的最大利润,最够超出内存限制

 public int maxProfit(int[] prices) {
            //dp表示i,j这段时间的最大利润
            int max = 0;
            if (prices.length == 0) return max;
            int[][] dp = new int[prices.length][prices.length];
            for (int i = 0; i < prices.length - 1; i++) {
                dp[i][i] = 0;
                dp[i][i + 1] = prices[i + 1] - prices[i];
                max = dp[i][i + 1] > max ? dp[i][i + 1] : max;
            }
            dp[prices.length - 1][prices.length - 1] = 0;

            for (int len = 2; len <= prices.length; len++) {
                for (int start = 0; start < prices.length; start++) {
                    if (start + len >= prices.length) {
                        break;
                    }
                    int end = start + len;

                    if ((prices[end] - prices[start]) > dp[start + 1][end - 1]) {
                        dp[start][end] = (prices[end] - prices[start]);
                    } else {
                        dp[start][end] = dp[start + 1][end - 1];

                    }
                    max = dp[start][end] > max ? dp[start][end] : max;
                }

            }
            return (int) max;

        }
题解2

其实这道题我们可以使用一维数组的动态规划实现,因为每两个相邻的数组存在差值,我们可以构建一个diff数组,然后求这些数组最大连续子序列和,即是我们的答案

//dp[i]表示以i结尾最大子序列和
//动态转移方程 dp[i] = dp[i - 1] + diff[i] > diff[i] ? dp[i - 1] + diff[i] : diff[i];
  public int maxProfit(int[] prices) {
            if (prices.length == 0||prices.length==1) 
              return 0;
            int[] diff = new int[prices.length - 1];
            for (int i = 0; i < prices.length - 1; i++) {
                diff[i] = prices[i + 1] - prices[i];
            }
            int[] dp = new int[diff.length];
            dp[0] = diff[0];
            int max=dp[0]>0?dp[0]:0;
            for (int i = 1; i < diff.length; i++) {
                dp[i] = dp[i - 1] + diff[i] > diff[i] ? dp[i - 1] + diff[i] : diff[i];
                max = max > dp[i] ? max : dp[i];
            }
            return max;

    }

题解3(在Leetcode官网上找到的归纳,状态机做法)

作者:labuladong
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/yi-ge-fang-fa-tuan-mie-6-dao-gu-piao-wen-ti-by-l-3/
来源:力扣(LeetCode)

其实我们可以把我们的买卖股票状态进行抽象

/*
dp[i][k][flag]
i表示第i天
k表示我们可以进行交易的次数
flag 表示我们的交易行为,1标售我们手里有股票,0表示我们手里没有股票
用上述三维数组可以表示所有的状态


确定状态转移方程

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
              max(选择 rest  ,选择 sell)

解释:今天我没有持有股票,有两种可能:
要么是我昨天就没有持有,然后今天选择 rest,所以我今天还是没有持有;
要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。

dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
              max(选择 rest  ,选择 buy)

解释:今天我持有着股票,有两种可能:
要么我昨天就持有着股票,然后今天选择 rest,所以我今天还持有着股票;
要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。

*/
 
当k=1(只允许交易一次)
/*
边界条件
dp[-1][k][0] = 0
解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0 。
dp[-1][k][1] = -infinity
解释:还没开始的时候,是不可能持有股票的,用负无穷表示这种不可能。
dp[i][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0 。
dp[i][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的,用负无穷表示这种不可能。



dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i])
dp[i][1][1] = max(dp[i-1][1][1], dp[i-1][0][0] - prices[i]) 
            = max(dp[i-1][1][1], -prices[i])
解释:k = 0 的 base case,所以 dp[i-1][0][0] = 0。

现在发现 k 都是 1,不会改变,即 k 对状态转移已经没有影响了。
可以进行进一步化简去掉所有 k:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], -prices[i])


*/

base case:
dp[-1][k][0] = dp[i][0][0] = 0
dp[-1][k][1] = dp[i][0][1] = -infinity

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


public int maxProfit(int[] prices) {
        if(prices.length==0) return 0;
         int[][] dp = new int[prices.length][2];
            for (int i = 0; i < prices.length; i++) {
                if (i == 0) {
                    //没买了股票
                    dp[i][0] = 0;
                    //买了股票
                    dp[i][1] = -prices[i];
                    continue;
                }

                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[prices.length - 1][0];
    }
当k=n(可以一直交易)
/*
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) 
            = max(dp[i-1][k][1], dp[i-1][k][0] - prices[i]) 
注意当k=n无穷大时,k等价于k-1都表示可以一直交易


!!!!我们的买入卖出记为一次交易,因此只买入算一次交易,卖出不在计算,因此买的时候可交易次数减一
卖的时候,交易次数不变

*/

 public int maxProfit(int[] prices) {

            if (prices.length == 0) return 0;
            int[][] dp = new int[prices.length][2];
            for (int i = 0; i < prices.length; i++) {
                if (i == 0) {
                    dp[i][0] = 0;
                    dp[i][1] = -prices[i];
                    continue;
                }
                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[prices.length - 1][0];

        }
//代码是可以简化的,这里为了比较清除的理解,所以这里没有简化的,有兴趣可以看看原作者的分析
当k=n(冷冻期,卖出后,必须至少等一天才能购买)
/*
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) 
            = max(dp[i-1][k][1], dp[i-2][k][0] - prices[i]) 
注意当k=n无穷大时,k等价于k-1都表示可以一直交易

!!!由于我们卖出后,不能连续再次购买,必须至少再等一次,才能进行购买 i-2

 转移函数基本一致,由于存在i-2我们需要对这部分初始值进行处理

*/
public int maxProfit(int[] prices) {
            if (prices.length == 0) return 0;
            int[][] dp = new int[prices.length][2];
            for (int i = 0; i < prices.length; i++) {
                if (i == 0) {
                    dp[i][0] = 0;
                    dp[i][1] = -prices[i];
                    continue;
                }
                if (i == 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]);//前一天就有了,第二种情况不存在,
                    continue;
                }
                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]);//不动、买进
            }
            return dp[prices.length - 1][0];

        }
k=2只允许交易两次
public int maxProfit(int[] prices) {
            if (prices.length == 0) return 0;
            int k = 2;//交易次数
            int[][][] dp = new int[prices.length][k + 1][2];
            for (int i = 0; i < prices.length; i++) {
                if (i == 0) {
                    //basecase
                    dp[i][2][0] = 0;//不可能
                    dp[i][2][1] = -prices[i];//不可能
                    dp[i][1][0] = 0;//不可能
                    dp[i][1][1] = -prices[i];//买入
                    continue;
                }
                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], 0 - prices[i]);//买


            }
            return dp[prices.length - 1][k][0];


        }
k=任意值
public int maxProfit(int k, int[] prices) {
            if (k == 0 || prices.length == 0) return 0;

            if (k > prices.length / 2) {
                //k为无穷的情况,与之前的代码重复
                int[][] dp = new int[prices.length][2];
                for (int i = 0; i < prices.length; i++) {
                    if (i == 0) {
                        dp[i][0] = 0;
                        dp[i][1] = -prices[i];
                        continue;
                    }
                    dp[i][1] = Math.max(dp[i - 1][1], dp[i-1][0] - prices[i]);//买入
                    dp[i][0] = Math.max(dp[i - 1][0], dp[i-1][1] + prices[i]);//卖出
                }
                return dp[prices.length - 1][0];

            }
            int[][][] dp = new int[prices.length][k + 1][2];
            for (int i = 0; i < prices.length; i++) {
                for (int j = 1; j <= k; j++) {
                    if (i == 0) {
                        dp[i][j][1] = -prices[i];
                        dp[i][j][0] = 0;
                        continue;
                    }
                    dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
                    dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);


                }

            }
            return dp[prices.length - 1][k][0];

    

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值