专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

122. 买卖股票的最佳时机 II

122. 买卖股票的最佳时机 II

100_1.png

解题思路

方法1:暴力搜索

可能暴力搜索不能让有些面试官满意,但我觉得可以以搜索算法为起点,考虑更好的算法。

根据题意,没有限制交易次数,在每一天,我们就可以根据当前是否持有股票选择相应的操作,这里 “暴力搜索” 也叫 “回溯搜索”、“回溯算法”,首先画出树形图。

100_2.png

class Solution {
    private int res;
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length < 2) {
            return 0;
 } int len = prices.length; this.res = 0; dfs(prices, 0, len, 0, res); return this.res; } /** * @param prices 股价数组 * @param index 当前是第几天,从0开始 * @param status 0 表示不持有股票,1 表示持有股票 * @param profit 当前收益 */ private void dfs(int[] prices, int index, int len, int status, int profit) { // 递归终止条件 if (index == len) { this.res = Math.max(this.res, profit); return; } dfs(prices, index + 1, len, status, profit); if (status == 0) { // 可以尝试转向1 dfs(prices, index + 1, len, 1, profit - prices[index]); } else { // 此时 status == 1,可以尝试转向 0 dfs(prices, index + 1, len, 0, profit + prices[index]); } } } 

超时在意料之中。注意看这个超时是在数据规模很大的时候,因此,可以说明搜索算法是正确的

100_3.png 100_4.png

方法2:贪心算法

使用贪心算法的流程是这样的:

  • 从第 i 天(这里 i >= 1)开始,与第 i - 1的股价进行比较,如果股价有上升(严格上升),就将升高的股价 (prices[i] - prices[i - 1]) 记入总利润,按照这种算法,得到的结果就是符合题意的最大利润。

下面对算法进行几点说明:

1、 该算法仅可以用于计算,但 计算的过程并不少真正交易的过程,但可以用贪心算法计算题目要求的最大利润。下面说明这个等价性:以 [1, 2, 3, 4] 为例,这4天的股价依次上身,按照贪心算法,得到的最大利润是:

# Java
res = (prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0]);
    = (prices[3] - prices[0]);

仔细观察上面的公式,按照贪心算法,在索引为 1、2、3 的这三天,我们做的操作应该是昨天买进的,今天卖出的,虽然这种操作题目并不允许,但是它等价于:“在索引为 0 的那一天买入,在索引为 3 的那一天卖出”。

1、 解释一下它为什么叫 “贪心算法”

贪心算法定义:

求解最优化的问题的算法通常需要经过一系列的步骤,在每个步骤都面临多种选择。对于许多最优化问题。使用动态规划算法来最优解有些杀鸡用牛刀了,可以使用更简单、更高效的算法。贪心算法(greedy algorithm)就是这样的算法。它在每一步都做出当时看起来最佳的选择,也就是说,它总数做出局部最优的选,寄希望这样的选择能导致全局最优解。

贪心算法在每一步总是做出在当前看来最好的选择。

因此

  • “贪心算法” 和 “动态规划”、“回溯搜索”算法一样,完成一件事情,是分布决策的;
  • “贪心算法”在每一步总是做出来当前看来最好的选择,这里理解“最好”的意思:
    • “最好”的意思往往根据题目而来,可能是“最小”,也可能是“最大”;
    • 贪心算法和动态规划相比,它即不看前面(也就是说它不需要从起那么的状态转移过来),也不看后面(无后效性,后面的选择不会对前面的选择有影响),因此贪心算法时间复杂度一般是线性的,空间复杂度是常数级别的。
    • 这道题“贪心”的地方在于,对于“今天的股价 – 昨天的股价”,得到的结果有 3 种可能:(1)整数 (2)0 (3)负数。 贪心算法的决策是: 只加整数
class Solution {
    public int maxProfit(int[] prices) {
        int res = 0;
        int len = prices.length;
        for (int i = 0; i< len - 1; i++) {
 int diff = prices[i + 1] - prices[i]; if(diff > 0) { res += diff; } } return res; } } 

等价写法:

class Solution {
    public int maxProfit(int[] prices) {
        int res = 0;
        int len = prices.length;
        for (int i = 0; i< len - 1; i++) {
 int += Math.max(prices[i + 1] - prices[i], 0); } return res; } } 

复杂度分享

  • 时间复杂度: O(N),这里 N表示股价的数组长度。
  • 空间复杂度: O(1)

方法3:动态规划

想到动态规划的原因是:可以用贪心算法解决的问题,一般情况下都可以用动态规划。因此,不妨从“状态”、“状态转移方程”的角度考虑一下,使用动态规划的思路解决这道问题。

思路:

1. 定义状态

状态 dp[i][j] 定义如下:

  • 第一维: i 表示索引为 i 的那一天( 具有前缀性质,即考虑了之前天数的收益)能获得的最大利润;
  • 第二维: j 表示索引为 i 的那一天持有股票,还是持有现金。这里 0表示持有现金 (cash) , 1 表示持有股票 (stock)

2. 思考状态转移方程

  • 状态从持有现金(cash)开始,到最后一天我们关心的状态一谈是持有现金(cash);
  • 每一天状态可以转移,也可以不懂。状态转移用下图表示: 100_5.png

    (状态转移方程写在代码中)

说明:

  • 因为不限制交易次数,除了最后一天,每一天的状态可能不变化,也可能转移。
  • 写代码的时候,可以不用对最后一天单独处理,输出最后一天,状态为 0 的时候值即可。

3. 确定起始

起始的时候:

  • 如果什么都不做, dp[0][0] = 0;
  • 如果买入股票,当前收益是负数,即 dp[0][1] = -prices[i];

4. 确定终止

终止的时候,输出 dp[len - 1][0],因为一定有 dp[len - 1][0] > dp[len - 1][1]

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]; } } 

复杂度分析: 时间复杂度:O(N),这里 N 表示股价数组的长度。 空间复杂度:O(N),虽然是二位数字,但是二维是常数,与问题规模无关。

也可以将状态数组分开设置,语义更明确/

public class Solution {

    public int maxProfit(int[] prices) {
        int len = prices.length;
        if (len < 2) {
 return 0; } // cash:持有现金 // hold:持有股票 // 状态数组 // 状态转移:cash → hold → cash → hold → cash → hold → cash int[] cash = new int[len]; int[] hold = new int[len]; cash[0] = 0; hold[0] = -prices[0]; for (int i = 1; i < len; i++) { // 这两行调换顺序也是可以的 cash[i] = Math.max(cash[i - 1], hold[i - 1] + prices[i]); hold[i] = Math.max(hold[i - 1], cash[i - 1] - prices[i]); } return cash[len - 1]; } } 

5. 考虑状态压缩

由于当前行只参考上一行,每一行就 2 个值,因此可以考虑使用“滚动变量”(“滚动数组”技巧)。

public class Solution {

    public int maxProfit(int[] prices) {
        int len = prices.length;
        if (len < 2) {
 return 0; } // cash:持有现金 // hold:持有股票 // 状态转移:cash → hold → cash → hold → cash → hold → cash int cash = 0; int hold = -prices[0]; int preCash = cash; int preHold = hold; for (int i = 1; i < len; i++) { cash = Math.max(preCash, preHold + prices[i]); hold = Math.max(preHold, preCash - prices[i]); preCash = cash; preHold = hold; } return cash; } } 

复杂度分析

  • 时间复杂度:O(N),这里 N 表示股价数组的长度。
  • 空间复杂度:O(1),分别使用两个滚动变量,将一纬数组状态压缩到常数。

100_6.png

部分图片来源于网络,版权归原作者,侵删。

文章永久链接:https://tech.souyunku.com/33863

未经允许不得转载:搜云库技术团队 » 122. 买卖股票的最佳时机 II

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们