动态规划中五道股票买卖题目详解

本文从属于笔者的数据结构与算法系列文章中的动态规划部分,同时也概括于笔者的个人校招准备之路:从Web前端到服务端应用架构这篇综述。前端

Leetcode上有五道关于股票买卖相关的问题,恰巧笔者面试阿里的时候也被问及,所以在本文中略做总结,这五个问题列举以下:java

简单买卖:只容许买卖一次

本题意思就是你获得一系列在接下来几天的股票价格,如今你被容许只用一次交易(就是买进再卖出)来获取最大利益。 这个很简单,只要用双指针的方法记住获利的大小,再筛选出最大的便可。代码以下:git

/**
 * @function 仅容许买卖一次
 * @description Say you have an array for which the ith element is the price of a given stock on day i.If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit.
 * @OJ https://leetcode.com/problems/best-time-to-buy-and-sell-stock/
 */
public class Stock1 {

    public int maxProfit(int[] prices) {

        //存储最大值的变量,初始化为0
        int ans = 0;

        //判断是否有效价格序列
        if (prices.length == 0) {
            return ans;
        }

        //判断应该在哪一日买入,即最小值
        int bought = prices[0];

        //遍历全部交易日价格
        for (int i = 1; i < prices.length; i++) {

            //判断本日是否可以卖出
            if (prices[i] > bought) {

                //判断若是本日卖出收益是否最大
                if (ans < (prices[i] - bought)) {
                    ans = prices[i] - bought;
                }
            } else {

                //判断本日是否为最低价格
                bought = prices[i];
            }
        }
        return ans;

    }
}

简单买卖:能够买卖无穷屡次

意思是买卖股票时能够不计买卖次数,可是必须在买以前先把之前的股票卖掉。而后求能获利最大的额度。 这样的话也很简单,只要碰见下一天的价格比这一天价格高的话,就卖出。代码以下:github

package wx.algorithm.op.dp.stock;

/**
 * Created by apple on 16/8/21.
 */

/**
 * @function 可以进行无穷屡次买卖, 求取最大值
 * @OJ https://github.com/wxyyxc1992/just-coder-handbook/blob/master/Algorithm/java/src/main/java/wx/algorithm/op/dp/stock/Stock2.java
 */
public class Stock2 {

    public int maxProfit(int[] prices) {
        int total = 0;

        //遍历全部交易日
        for (int i = 0; i < prices.length - 1; i++) {
            //只要是后一天比前一天贵,就卖出
            if (prices[i + 1] > prices[i]) total += prices[i + 1] - prices[i];
        }

        return total;
    }
}

最多买卖两次

如今你被容许买卖的次数减小到最多交易两次,也是必须先卖出再买进。而后求能获利的最大额度。 面试

咱们来琢磨一下这个规则,每次交易必须先卖掉手上这只股票才能进行下次操做,好比下面几天的股票价格为【1,7,15,6,57,32,76】,买第一只股票价格为¥1,为了最大获利,在¥76时卖掉确定最好,因为交易时必须先卖再买,因此要是这么交易的话,只能交易一次。可是如今咱们有两次买卖机会,也许在这几天中,中间两次的交易就的获利总和就能超过¥76-¥1=¥75,因此咱们要来找到这个组合。既然交易不能交叉(must sell the stock before you buy again),也就是说咱们在遍历价格差找最大获益时,能够首位同时进行。能够用双向动态规划的思想来作。代码以下算法

package wx.algorithm.op.dp.stock;

/**
 * Created by apple on 16/8/21.
 */

/**
 * @function 最多两次买卖
 * @OJ https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/
 */
public class Stock3 {

    public static int maxProfit(int[] prices) {

        //判断是否为有效交易天数
        if (prices.length == 0) return 0;

        //存放左半部分最大收益
        int[] left = new int[prices.length];

        //存放右半部分最大收益
        int[] right = new int[prices.length];

        //初始化为0
        int leftMin = prices[0];

        int rightMax = prices[prices.length - 1];

        //总收益
        int sum = 0;

        //计算左半段最大收益
        for (int i = 1; i < prices.length; i++) {

            //获取左半部分的最低价
            leftMin = Math.min(prices[i], leftMin);

            //获取左半部分最大收益
            left[i] = Math.max(prices[i] - leftMin, left[i - 1]);
        }

        //计算右半段最大收益
        for (int i = prices.length - 2; i >= 0; i--) {

            //获取右半部分最低价
            rightMax = Math.max(prices[i], rightMax);

            //获取右半部分最大收益
            right[i] = Math.max(rightMax - prices[i], right[i + 1]);
        }
        //找出两次交易最大收益组合
        for (int i = 0; i < prices.length; i++) {
            if ((left[i] + right[i]) > sum) sum = left[i] + right[i];
        }
        return sum;
    }

    public static void main(String args[]) {
        int[] prices = new int[]{1, 7, 15, 6, 57, 32, 76};

        System.out.print("maxProfit is" + maxProfit(prices));
    }
}

在上面left和right的计算当中,left存储的获利为【¥0,¥6,¥14,¥14,¥56,¥56,¥75】,right存储的获利为【¥75,¥70,¥70,¥70,¥44,¥44,¥0】,能够看出买入第一次股票,在最后一天卖出股票获利是作多的是¥75,可是因为能够交易两次,咱们能够看出在¥1时买入,¥57时卖出,再在¥32时买入,在¥76卖出,获利是最多的,一共是¥100.数组

最多买卖K次

此状况下容许用户买卖屡次,以期获取最大值。最方便想到的办法会是回溯,不过不可避免的会致使重复计算,所以最好的仍是进行动态规划。当咱们考虑如何构建动态规划的状态转换函数时,首先假想下咱们构建的状态转移表,应该会包含k,即咱们的交易次数与i,即交易天数这两个变量。不过本题中存在的问题是每一个i同时还存在三个状态,即i天买入或者卖出或者啥都不作,抽象成两个状态的就是第i天是持有股票仍是不持有股票。咱们能够为此创建两个向量:数据结构

  • hold[i][j]:对于0~i天中最多进行j次交易而且第i天仍然持有股票的收益架构

  • unhold[i][j]:对于0~i天中最多进行j次交易而且第i天不持有股票的收益app

第i天持有股票的收益为 以前买了股票但尚未卖出 或者 今天才选择买入股票 两者中较大值

$$
holdi = Math.max(unholdi-1-prices[i],holdi-1)
$$

第i天不持有股票的收益为 选择今天卖出 或者 今天不买入时 最大的收益

$$ unholdi = Math.max(holdi-1+prices[i],unholdi-1) $$

代码以下

package wx.algorithm.op.dp.stock;

/**
 * Created by apple on 16/8/21.
 */
public class Stock4 {

    /**
     * @param k      可交易的次数
     * @param prices 价格向量
     * @return
     * @function 计算最多K次状况下能得到的最大理论
     */
    public int maxProfit(int k, int[] prices) {
        if (k == 0 || prices.length < 2)
            return 0;
        if (k > prices.length / 2)
            return noLimit(prices);

        // hold[i][j]: 对于0~i天中最多进行j次交易而且第i天仍然持有股票的收益
        // unhold[i][j]: 对于0~i天中最多进行j次交易而且第i天不持有股票的收益
        // 第i天持有股票的收益为 以前买了股票但尚未卖出 或者 今天才选择买入股票 两者中较大值
        // hold[i][j] = Math.max(unhold[i-1][j]-prices[i],hold[i-1][j]);
        // 第i天不持有股票的收益为 选择今天卖出 或者 今天不买入时 最大的收益
        // unhold[i][j] = Math.max(hold[i-1][j-1]+prices[i],unhold[i-1][j]);

        int[][] hold = new int[k + 1][prices.length];
        int[][] unhold = new int[k + 1][prices.length];
        for (int i = 1; i <= k; i++) {

            //初始化持有状态下的初始值
            hold[i][0] = -prices[0];

            //初始化不持有状态下的初始值 为0
            unhold[i][0] = 0;
            for (int j = 1; j < prices.length; j++) {
                hold[i][j] = Math.max(-prices[j] + unhold[i - 1][j], hold[i][j - 1]); // Buy or not buy
                unhold[i][j] = Math.max(prices[j] + hold[i][j - 1], unhold[i][j - 1]); // Sell or not sell
            }
        }

        //真实状况下最后一天了仍是要卖出的
        return unhold[k][prices.length - 1];
    }

    private int noLimit(int[] prices) { // Solution from Best Time to Buy and Sell Stock II
        int max = 0;
        for (int i = 0; i < prices.length - 1; i++) {
            if (prices[i + 1] > prices[i])
                max += prices[i + 1] - prices[i];
        }
        return max;
    }

}

T+2规则

本题意为用户在卖出以后须要休息一天才能进行操做,那么在本题中用户是存在有三个状态,未持有(unhold)、持有(hold)、休息(cooldown)这三种,这三种状态的状态转化图为:

其对应的状态转换函数为:

unhold[i] = max(unhold[i - 1], cooldown[i - 1]); // Stay at s0, or rest from s2
hold[i] = max(hold[i - 1], unhold[i - 1] - prices[i]); // Stay at s1, or buy from s0
cooldown[i] = hol[i - 1] + prices[i]; // Only one way from s1

代码为:

package wx.algorithm.op.dp.stock;

/**
 * Created by apple on 16/8/21.
 */
public class StockWithCoolDown {

    public int maxProfit(int[] prices) {

        //判断日期长度是否大于1
        if (prices.length <= 1) {
            return 0;
        }

        //构建三个状态数组
        int[] unhold = new int[prices.length];

        int[] hold = new int[prices.length];

        int[] cooldown = new int[prices.length];

        unhold[0] = 0;

        hold[0] = -prices[0];

        cooldown[0] = Integer.MIN_VALUE;

        for (int i = 1; i < prices.length; i++) {
            unhold[i] = Math.max(unhold[i - 1], cooldown[i - 1]);
            hold[i] = Math.max(hold[i - 1], unhold[i - 1] - prices[i]);
            cooldown[i] = hold[i - 1] + prices[i];
        }

        return Math.max(unhold[prices.length - 1],cooldown[prices.length - 1]);

    }
}
相关文章
相关标签/搜索