LeetCode 第 121 题:买卖股票的最佳时机(动态规划)

本文详细解析了如何在给定的股票价格数组中找到最佳的买卖时机以获取最大利润。通过直观的假设修正法(打擂台法)和标准动态规划方法,提供了Java和C++的实现代码,同时对比了不同状态压缩技巧对代码简洁性和效率的影响。

地址:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
我写的题解地址:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/bao-li-mei-ju-dong-tai-gui-hua-chai-fen-si-xiang-b/

Java 代码:

public class Solution {

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

        int res = 0;

        // 表示在当前位置之前的最小值,假设修正法(打擂台法)
        int minVal = prices[0];
        // 注意:这里从 1 开始
        for (int i = 1; i < len; i++) {
            res = Math.max(res, prices[i] - minVal);
            minVal = Math.min(minVal, prices[i]);
        }
        return res;
    }
}

C++ 代码:

#include <iostream>
#include <vector>

using namespace std;

class Solution {
public:
    int maxProfit(vector<int> &prices) {
        // 特判
        int size = prices.size();
        if (size == 0) {
            return 0;
        }

        int minPrice = prices[0];
        int res = 0;
        for (int i = 1; i < size; i++) {
            res = max(res, prices[i] - minPrice);
            minPrice = min(minPrice, prices[i]);
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度:O(N)O(N)O(N)
  • 空间复杂度:O(1)O(1)O(1)

使用标准动态规划的写法

题目中关键的地方:

1、只交易一次。这里的“交易”指的是“买入和卖出一支股票”,即发生一次交易得用 2 天时间;

2、如果股价是这样:[5, 4, 3, 2, 1] 先买后卖一定亏钱,全程不交易即可。

  • 定义状态:dp[i][j] 表示 [0, i] 子区间里交易一次能够获得的最大利润。

说明:1、这里 j 只有两个取值:

0:用户手上不持股所能获得的最大利润,特指卖出股票以后的不持股,不是指没有进行过任何交易的不持股;

1:用户手上持股所能获得的最大利润。

  • 状态转移方程:
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
dp[i][1] = max(dp[i - 1][1], -prices[i])
  • 初始化:dp[0][0] = 0dp[0][1] = -prices[0]

说明:第 0 天不持股的时候,显然没有交易过,因此利润为 0;第 0 天持股的时候,显然只购买了一股,因此利润为 -prices[0]

  • 输出:dp[len - 1][0],输出一定是最后一天不持股的情况。我们定义的状态是有前缀性质的,因此只要把最后一天的状态输出即可。

Java 代码:

public class Solution {

    // 动态规划

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

        // 交易的不持股
        // 1:用户手上持股所能获得的最大利润

        // 注意:因为题目限制只能交易一次,因此状态只可能从 1 到 0,不可能从 0 到 1
        // 
        
        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], -prices[i]);
        }
        return dp[len - 1][0];
    }
}

状态压缩1:使用滚动数组

Java 代码:

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

        int[][] dp = new int[2][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for (int i = 1; i < len; i++) {
            dp[i & 1][0] = Math.max(dp[(i - 1) & 1][0], dp[(i - 1) & 1][1] + prices[i]);
            dp[i & 1][1] = Math.max(dp[(i - 1) & 1][1], -prices[i]);
        }
        return dp[(len - 1) & 1][0];
    }
}

状态压缩 2:直接把第 1 行压缩了

Java 代码:

public class Solution {

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

        int[] dp = new int[2];
        dp[0] = 0;
        dp[1] = -prices[0];
        for (int i = 1; i < len; i++) {
            dp[0] = Math.max(dp[0], dp[1] + prices[i]);
            dp[1] = Math.max(dp[1], -prices[i]);
        }
        return dp[0];
    }
}
  • 差分数组。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值