Best Time to Buy and Sell Stock

关键字:leetcode,Best Time To Buy And Sell Stock,算法,algorithm,动态规划,dynamic programming

leetcode 上关于Best Time to Buy and Sell Stock主题的题目有四个:

  • https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/

  • https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/

  • https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/

  • https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/

这四个题目难度依次递增。大致意思就是,给我们一个 List<Integer> prices ,然后让我们找到怎么买卖才能获得最大收益。其中第四个问题是寻求一个通解,在给定 prices和最大买卖次数k的情况下,求最大收益。

首先大致的解题方向是动态规划,这个应该不难想到。之后就是怎么找到状态,怎么列状态转移方程。考虑某一天的情况,可以有如下三种状态:

  • 当天买入

  • 当天卖出

  • 当天什么也没做

因为这个题目我们要找到最大收益,所以,如果最后一天的状态是买入的话,那么其收益一定不是最大的,因为最后一天买入的话,就没有机会卖出了。那么,上面的三个状态可以减少到两个:

  1. 当天卖出。

  2. 卖出,但是不在当天(即在前面的某一天)。

所以,我们用 soldAtToday[k] 来表示当天卖出且卖出的时候交易了 k 手的时候的最大收益,soldNotAtToday[k] 代表在之前某天卖出,且当前交易手数为 k 的时候的最大收益。那么,状态转移方程就可以用如下伪代码描述:

soldAtToday[k] =  max(soldAtYesterday[k] + price, soldNotAtYesterday[k - 1] + price);
soldNotAtToday[k] = max(soldAtYesterday[k], soldNotAtYesterday[k]);
复制代码

其中,k 是交易次数。需要注意的是soldAtToday[k] = max(soldAtYesterday[k] + price, soldNotAtYesterday[k - 1] + price);中第一个备选项是soldAtYesterday[k],而不是soldAtYesterday[k - 1],其含义就是,把本应该昨天卖的延长一天到今天卖,所以交易次数还是 k 。

具体实现的时候,我们发现 soldAtToday 和 soldNotAtToday 都是只依赖于 soldAtYesterday 和 soldNotAtYesterday,所以我们可以利用两个长度为 k + 1 的数组来完成 today 和 yesterday 数据的存储。

package BestTimeToBuyAndSellStock.VersionIV;

@SuppressWarnings("Duplicates")
class Solution {

    private int quickSolve(int[] prices) {
        int len = prices.length, profit = 0;
        for (int i = 1; i < len; i++) {
            // as long as there is a price gap, we gain a profit.
            if (prices[i] > prices[i - 1]) profit += prices[i] - prices[i - 1];
        }
        return profit;
    }

    public int maxProfit(int k, int[] prices) {
        if (prices == null || prices.length <= 1 || k <= 0) {
            return 0;
        }

        int len = prices.length;

        if (k >= len / 2) return quickSolve(prices);


        int today = 1;
        int yesterday = 0;
        int[][] soldAt = new int[2][k + 1];
        int[][] soldNotAt = new int[2][k + 1];

        soldAt[today][0] = 0;
        soldAt[yesterday][0] = 0;
        soldNotAt[today][0] = 0;
        soldNotAt[yesterday][0] = 0;

        for (int i = 0; i < k + 1; i++) {
            soldAt[yesterday][i] = 0;
            soldNotAt[yesterday][i] = 0;
        }

        for (int i = 1; i < prices.length; i++) {
            int price = prices[i] - prices[i - 1];
            for (int j = 1; j < k + 1; j++) {
                soldAt[today][j] = Math.max(
                        soldAt[yesterday][j] + price,
                        soldNotAt[yesterday][j - 1] + price
                );
                soldNotAt[today][j] = Math.max(
                        soldAt[yesterday][j],
                        soldNotAt[yesterday][j]
                );
            }
            int tmp = yesterday;
            yesterday = today;
            today = tmp;
        }

        return Math.max(soldAt[yesterday][k], soldNotAt[yesterday][k]);
    }
}


复制代码

观察代码发现,开头多了一个 quickSolve 。这是因为,如果直接把上面我们描述的算法实现出来,提交上去,会出现 TimeLimted 的问题。仔细分析了一下超时的用例,发现他们的特征是 k 很大。其实,当 k 大于 prices.length / 2 的时候,就相当于没有 k 的限制,即随便买卖了,因为不考虑当天买当天卖的情况,或者把当天买卖的收益虚拟的记为 0 。那么我们的算法就退化成最简单的情况,于是使用一个 quickSolve 来解决即可。 这个题目也对我们日常工作提供了启示:出现问题了,找到瓶颈,分析特征,然后突破。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值