Leetcode投资的最大收益(动态规划/贪心算法)

简介: Leetcode投资的最大收益(动态规划/贪心算法)

Leetcode121股票只能买卖一次,问最大利润


示例 :

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


思路分析:


法1:双指针:定义双指针,一个指向买入,一个指向卖出,遍历数组,更新最大值。


法2:贪心:因为只有一次买入卖出,保证以最低的价钱买入,然后遍历数组,更新最大值。


法3:动态规划:一天只有两个状态:持有和不持有,遍历数组返回最后一天的情况


  • dp[i][0]和dp[i][1]分别代表第i天持有和不持有股票所得的现金。
  • 状态转移方程:对于当前填持有和不持有,我们分别分析其上一天的情况
  • 持有状态,上一天持有或不持有(现在买入,即-prices[i])最大值;
  • 不持有状态,上一天可以是持有(现在卖了,即prices[i - 1][0] + prices[i])或不持有最大值。


代码实现:代码1:双指针

public int maxProfit(int[] prices) {
    if (prices == null || prices.length == 0) return 0;
    int start = 0, end = 1, max = 0;
    while (start < end && end < prices.length) {
        if (prices[start] >= prices[end]) {
            start = end++;
        } else {
            max = Math.max(max, prices[end] - prices[start]);
            end++;
        }
    }
    return max;
}


代码2:贪心

public int maxProfit(int[] prices) {
    if (prices == null || prices.length == 0) return 0;
    int low = Integer.MAX_VALUE;
    int max = 0;
    for (int i = 0; i < prices.length; ++i) {
        low = Math.min(low, prices[i]);
        max = Math.max(max, prices[i] - low);
    }
    return max;
}


代码3:动态规划

public int maxProfit(int[] prices) {
    int len = prices.length;
    if (prices == null || len == 0) return 0;
    int[][] dp = new int[len][2];
    dp[0][0] = -prices[0];
    dp[0][1] = 0;
    for (int i = 1; i < len; ++i) {
        dp[i][0] = Math.max(dp[i - 1][0], - prices[i]);
        dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
    }
    return dp[len - 1][1];
}


Leetcode122,可以多次买卖股票,问最大收益。


示例:

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。


思路分析:


法1:贪心算法:实现上述需求每次交易只要买入小于卖出,即保证每次交易都有收益即可!


法2:动态规划:这里与上题唯一的区别是多次买卖股票。关键点


  • dp[i][0],即第i天持有股票时,是在之前利润(前一天卖出的最大利润,前一天必须卖出)基础上在买入,即dp[i - 1][1] - prices[i],上题只有一次买入。
  • 对于第i天不持有股票,与上边相同。


代码实现:代码1:贪心

public int maxProfit(int[] prices) {
    int n = prices.length;
    if (prices == null || n == 0) return 0;
    int max = 0;
    for (int i = 1; i < n; i++) {
        if (prices[i] > prices[i - 1]) {
            max += prices[i] - prices[i - 1];
        }
    }
    return max;
}


代码2:动态规划

public int maxProfit(int[] prices) {
    int len = prices.length;
    if (prices == null || len == 0) return 0;
    int[][] dp = new int[len][2];
    dp[0][0] = -prices[0];
    dp[0][1] = 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][1];
}


Leetcode123,最多买卖两次,问最大收益。


示例 :

输入: [3,3,5,0,0,3,1,4]
输出: 6
解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
     随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。


思路分析:


与案例2不同的是,本例限制买入卖出的次数,最多买入两次。相对来说,难度大了很多。对于下面的情况:

1 5 2 8 3 10


第一天买第二天卖,第三天买第四天卖,第五天买第六天卖,三次收益分别是 467,最高的两次就是 6 + 7 = 13 了,但是我们第二天其实可以不卖出,第四天再卖出,那么收益是 8 - 1 = 7,再加上第五天买入第六天卖出的收益就是 7 + 7 = 14了。


所以当达到了一个高点时不一定要卖出!!!---用动态规划(动态规划关键就是数组定义和状态转移方程!!!)


法1:动态规划:当然我们可以暴力的想一下,当我们计算第 i 天的收益时进行了k次交易,我们可以选择:


  • 不买入卖出,即第 i 天的收益 等于第 i - 1 天的收益.
  • 卖出,追求更大收益,这里就要在0i-1 天就要选择一天买入。多选择了一次买入,那在买入之前已经进行了 k-1 次买卖。


即:当用 dp[i][k] 表示前i天最多交易k次的最高收益(数组定义),求解可分为两部分(状态转移)


  • 不操作:dp[i][k] = dp[i-1][k]
  • 之前有买入:在第 j 天买入,收益就是 prices[i] - (prices[j] - dp[j][k-1]),我们利用动态规划找第j天(prices[j] - dp[j][k-1])最小值,即代码中的min


法2:状态机,一共有五种状态:不操作,第一次买入/卖出和第二次买入/卖出。


  • dp[i][j]表示第i天,状态为j所剩最大现金。,注:dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态
  • 对于i天都有两个状态:持有和不持有,我们分别考虑前一天的状态。参照122题求解,只不过我们进行两次这样的操作。


代码实现:代码1:动态规划

public int maxProfit(int[] prices) {
    int n = prices.length;
    if (prices == null || n == 0) return 0;
    int K = 2;
    int[][] dp = new int[n][K + 1];
    for (int k = 1; k <= K; k++) {
        int min = prices[0];
        for (int i = 1; i < n; i++) {
            //找出第 1 天到第 i 天 prices[buy] - dp[buy][k - 1] 的最小值 
            min = Math.min(prices[i] - dp[i][k - 1], min); 
            //比较不操作和选择一天买入的哪个值更大
            dp[i][k] = Math.max(dp[i - 1][k], prices[i] - min);
        }
    }
    return dp[n - 1][K];
}


代码2:状态机

public int maxProfit(int[] prices) {
    int n = prices.length;
    if (prices == null || n == 0) return 0;
    int[][] dp = new int[n][5];
    dp[0][1] = dp[0][3] = -prices[0];
    for (int i = 1; i < n; ++i) {
        dp[i][0] = dp[i - 1][0];
        dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); 
        dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]); 
        dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]); 
        dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]); 
    }
    return dp[n - 1][4];
}


Leetcode188,最多买卖K次,问最大收益。


示例 :

输入:k = 2, prices = [2,4,1]
输出:2
解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。


思路分析:动态规划:无论买卖多少次,每天我们都对应两个状态。我们这里使用123中状态机的解法:


  • dp[i][j] 表示第i天,状态为j,所剩下的最大现金是dp[i][j]
  • j的状态表示为:0 表示不操作;1 第一次买入,2 第一次卖出;3 第二次买入,4 第二次卖出...  即除了0之外,奇数状态买入,偶数状态卖出。
  • 注意j的步长为2(每天),共进行2*k次买入卖出,注意j循环时不要越界!测试用例中含有0,特判!


状态转移方程为:

dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);


  • 注意初始条件:当卖出,即dp[0][j] == 0,  j为偶数;当买入,即dp[0][j] = -prices[0],j为奇数。
  • 按照定义最终有2k个状态,所以返回 dp[n - 1][2 * k]


时间复杂度:O(KN),K为交易的次数。


代码实现:

public int maxProfit(int k, int[] prices) {
    int n = prices.length;
    if (prices == null || n == 0) return 0;
    int[][] dp = new int[n][2 * k + 1];
    for (int j = 1; j < 2 * k; j += 2) {
        dp[0][j] = -prices[0];
    }
    for (int i = 1; i < n; i++) {
        for (int j = 0; j < 2 * k - 1; j += 2) {
            dp[i][j + 1] = Math.max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
            dp[i][j + 2] = Math.max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
        }
    }
    return dp[n - 1][2 * k];
}


Leetcode309,  可以多次买卖但每次卖出有冷冻期1天。


示例 :

输入: [1,2,3,0,2]
输出: 3 
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]


思路分析:动态规划:本题在122基础上加入冷冻期,这里我们第i天分为三种状态,分别用0~2表示:


  • 持有股票:可能是前一天就持有或者度过冷冻期又买入(不能直接买入)的最大值。
  • 不持有股票,处于冷冻期:原因是当前卖出了股票,得到收益。
  • 不持有股票,不在冷冻期:可能前一天在冷冻期或者不在冷冻期的最大值。


注意:最后我们需要返回不持有股票的最大值。


代码实现:

public int maxProfit(int[] prices) {
    int n = prices.length;
    if (prices == null || n == 0) return 0;
    int[][] dp = new int[n][3];
    dp[0][0] = -prices[0];
    for (int i = 1; i < n; ++i) {
        dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i]);
        // 不持有股票
        dp[i][1] = dp[i - 1][0] + prices[i];
        dp[i][2] = Math.max(dp[i - 1][1], dp[i - 1][2]);
    }
    return Math.max(dp[n - 1][1], dp[n - 1][2]);
}


Leetcode714,  可以多次买卖,但每次有手续费。


示例 :

输入: prices = [1, 3, 2, 8, 4, 9], fee = 2
输出: 8
解释: 能够达到的最大利润:  
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.


思路分析:本题在122基础上加入手续费,我们只需要在每次卖出时减去手续费即可(即需要支付手续费)!


代码实现:

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


相关文章
|
2月前
|
存储 算法
深入了解动态规划算法
深入了解动态规划算法
58 1
|
2月前
|
算法 测试技术 C++
【动态规划算法】蓝桥杯填充问题(C/C++)
【动态规划算法】蓝桥杯填充问题(C/C++)
|
2月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
39 0
|
19天前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
19天前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
33 2
|
2月前
|
算法
动态规划算法学习三:0-1背包问题
这篇文章是关于0-1背包问题的动态规划算法详解,包括问题描述、解决步骤、最优子结构性质、状态表示和递推方程、算法设计与分析、计算最优值、算法实现以及对算法缺点的思考。
69 2
动态规划算法学习三:0-1背包问题
|
2月前
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
27 2
|
2月前
|
算法
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
这篇文章介绍了动态规划算法中解决最大上升子序列问题(LIS)的方法,包括问题的描述、动态规划的步骤、状态表示、递推方程、计算最优值以及优化方法,如非动态规划的二分法。
71 0
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
|
2月前
|
算法
动态规划算法学习二:最长公共子序列
这篇文章介绍了如何使用动态规划算法解决最长公共子序列(LCS)问题,包括问题描述、最优子结构性质、状态表示、状态递归方程、计算最优值的方法,以及具体的代码实现。
144 0
动态规划算法学习二:最长公共子序列
|
2月前
|
存储 人工智能 算法
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)
【算法——动态规划】蓝桥ALGO-1007 印章(C/C++)