股票交易问题合集

2021-12-12  本文已影响0人  Phoebe_Liu

121. 买卖股票的最佳时机——只允许交易一次 动态规划 || 一次遍历

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
第二种动态规划

public class Solution {

    public int maxProfit(int[] prices) {
        int len = prices.length;
        // 特殊判断
        if (len < 2) {
            return 0;
        }
        int[][] dp = new int[len][2];

        // dp[i][0] 下标为 i 这天结束的时候,不持股,手上拥有的现金数
        // dp[i][1] 下标为 i 这天结束的时候,持股,手上拥有的现金数

        // 初始化:不持股显然为 0,持股就需要减去第 1 天(下标为 0)的股价
        dp[0][0] = 0;
        dp[0][1] = -prices[0];

        // 从第 2 天开始遍历
        for (int i = 1; i < len; 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[len - 1][0];
    }
}

122. 买卖股票的最佳时机 II —— 允许交易无数次,贪心||动态规划

给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
贪心算法:
时间复杂度 O(N) : 只需遍历一次price;
空间复杂度 O(1): 变量使用常数额外空间。

class Solution {
    public int maxProfit(int[] prices) {
        int profit = 0;
        for (int i = 1; i < prices.length; i++) {
            int tmp = prices[i] - prices[i - 1];
            if (tmp > 0) profit += tmp;
        }
        return profit;
    }
}

动态规划:
dp[i][j] 表示到下标为 i 的这一天,持股状态为 j 时,我们手上拥有的最大现金数。
确定初始值:起始如果什么都不做,dp[0][0] = 0;如果持有股票,当前拥有的现金数是当天股价的相反数,即 dp[0][1] = -prices[i];
时间复杂度:O(N),这里 NN 表示股价数组的长度;
空间复杂度:O(N),虽然是二维数组,但是第二维是常数,与问题规模无关。

public class Solution {

    public int maxProfit(int[] prices) {
        int len = prices.length;
        if (len < 2) {
            return 0;
        }

        // 0:持有现金
        // 1:持有股票
        // 状态转移:0 → 1 → 0 → 1 → 0 → 1 → 0
        int[][] dp = new int[len][2];

        dp[0][0] = 0;
        dp[0][1] = -prices[0];

        for (int i = 1; i < len; 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[len - 1][0];
    }
}

123. 买卖股票的最佳时机 III —— 最多买卖2次 动态规划 || 头尾双指针

给定一个数组,它的第i 个元素是一支给定的股票在第 i天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 **两笔 **交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
第一种:动态规划
一天结束时,可能有持股、可能未持股、可能卖出过1次、可能卖出过2次、也可能未卖出过
所以定义状态转移数组dp[天数][当前是否持股][卖出的次数]
具体一天结束时的6种状态:

class Solution:
    def maxProfit(self, prices):
        if prices==[]:
            return 0
        length=len(prices)
        #结束时的最高利润=[天数][是否持有股票][卖出次数]
        dp=[ [[0,0,0],[0,0,0] ] for i in range(0,length) ]
        #第一天休息
        dp[0][0][0]=0
        #第一天买入
        dp[0][1][0]=-prices[0]
        # 第一天不可能已经有卖出
        dp[0][0][1] = float('-inf')
        dp[0][0][2] = float('-inf')
        #第一天不可能已经卖出
        dp[0][1][1]=float('-inf')
        dp[0][1][2]=float('-inf')
        for i in range(1,length):
            #未持股,未卖出过,说明从未进行过买卖
            dp[i][0][0]=0
            #未持股,卖出过1次,可能是今天卖的,可能是之前卖的
            dp[i][0][1]=max(dp[i-1][1][0]+prices[i],dp[i-1][0][1])
            #未持股,卖出过2次,可能是今天卖的,可能是之前卖的
            dp[i][0][2]=max(dp[i-1][1][1]+prices[i],dp[i-1][0][2])
            #持股,未卖出过,可能是今天买的,可能是之前买的
            dp[i][1][0]=max(dp[i-1][0][0]-prices[i],dp[i-1][1][0])
            #持股,卖出过1次,可能是今天买的,可能是之前买的
            dp[i][1][1]=max(dp[i-1][0][1]-prices[i],dp[i-1][1][1])
            #持股,卖出过2次,不可能
            dp[i][1][2]=float('-inf')
        return max(dp[length-1][0][1],dp[length-1][0][2],0)

第二种:头尾双指针
以i为分界线,[0,i]最大 + [i+1,len-1]最大。
从左向右遍历很简单的能求出[0,i]最大,从左向右遍历过程中记录最小值,当前值与最小值求差,这个差的最大值就是[0,i]最大
类似的能求出[i+1,len-1]最大,倒着做。为了便于查找,用数组maxs[]记录每个位置右侧差的最大值。
实际编码中我选择先做[i+1,len-1]最大。在做[0,i]最大过程中,结合maxs[]求出答案。

class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        int max = 0;
        int[] maxs = new int[len + 1];
        for (int i = len - 1; i >= 0; i--) {
            int price = prices[i];
            max = Math.max(max, price);// [i,len-1]位置右侧的最大值
            maxs[i] = Math.max(maxs[i + 1], max - price);// [i,len-1]最大差值
        }
        int min = Integer.MAX_VALUE;
        max = 0;
        for (int i = 0; i < len; i++) {
            int price = prices[i];
            min = Math.min(min, price);// [0,i]最小值
            max = Math.max(max, price - min + maxs[i + 1]);// [0,i]最大值 + [i+1,len-1]最大值
        }
        return max;
    }
}
class Solution {
    public int maxProfit(int k, int[] prices) {
        if (prices.length == 0) {
            return 0;
        }

        int n = prices.length;
        k = Math.min(k, n / 2);
        int[][] buy = new int[n][k + 1];
        int[][] sell = new int[n][k + 1];

        buy[0][0] = -prices[0];
        sell[0][0] = 0;
        for (int i = 1; i <= k; ++i) {
            buy[0][i] = sell[0][i] = Integer.MIN_VALUE / 2;
        }

        for (int i = 1; i < n; ++i) {
            buy[i][0] = Math.max(buy[i - 1][0], sell[i - 1][0] - prices[i]);
            for (int j = 1; j <= k; ++j) {
                buy[i][j] = Math.max(buy[i - 1][j], sell[i - 1][j] - prices[i]);
                sell[i][j] = Math.max(sell[i - 1][j], buy[i - 1][j - 1] + prices[i]);   
            }
        }

        return Arrays.stream(sell[n - 1]).max().getAsInt();
    }
}
上一篇下一篇

猜你喜欢

热点阅读