文章目录
一、类似装配线的dp
考虑到「不能同时参与多笔交易」,因此每天交易结束后只可能存在手里有一支股票或者没有股票的状态。
定义状态 dp [ i ] [ 0 ] \textit{dp}[i][0] dp[i][0] 表示第 i i i 天交易完后手里没有股票的最大利润, dp [ i ] [ 1 ] \textit{dp}[i][1] dp[i][1] 表示第 i i i 天交易完后手里持有一支股票的最大利润( i i i 从 0 开始)。
考虑 dp [ i ] [ 0 ] \textit{dp}[i][0] dp[i][0] 的转移方程:
如果这一天交易完后手里没有股票,那么可能的转移状态为前一天已经没有股票,即 dp [ i − 1 ] [ 0 ] \textit{dp}[i-1][0] dp[i−1][0],或者前一天结束的时候手里持有一支股票,即 dp [ i − 1 ] [ 1 ] \textit{dp}[i-1][1] dp[i−1][1],这时候我们要将其卖出,并获得 prices [ i ] \textit{prices}[i] prices[i] 的收益。因此为了收益最大化,我们列出如下的转移方程:
dp [ i ] [ 0 ] = max { dp [ i − 1 ] [ 0 ] , dp [ i − 1 ] [ 1 ] + prices [ i ] } \textit{dp}[i][0]=\max\{\textit{dp}[i-1][0],\textit{dp}[i-1][1]+\textit{prices}[i]\} dp[i][0]=max{dp[i−1][0],dp[i−1][1]+prices[i]}
再来考虑 dp [ i ] [ 1 ] \textit{dp}[i][1] dp[i][1]
按照同样的方式考虑转移状态,那么可能的转移状态为前一天已经持有一支股票,即 dp [ i − 1 ] [ 1 ] \textit{dp}[i-1][1] dp[i−1][1],或者前一天结束时还没有股票,即 dp [ i − 1 ] [ 0 ] \textit{dp}[i-1][0] dp[i−1][0],这时候我们要将其买入,并减少 prices [ i ] \textit{prices}[i] prices[i] 的收益。可以列出如下的转移方程:
dp [ i ] [ 1 ] = max { dp [ i − 1 ] [ 1 ] , dp [ i − 1 ] [ 0 ] − prices [ i ] } \textit{dp}[i][1]=\max\{\textit{dp}[i-1][1],\textit{dp}[i-1][0]-\textit{prices}[i]\} dp[i][1]=max{dp[i−1][1],dp[i−1][0]−prices[i]}
对于初始状态
根据状态定义我们可以知道第 0 天交易结束的时候 dp [ 0 ] [ 0 ] = 0 , dp [ 0 ] [ 1 ] = − prices [ 0 ] \textit{dp}[0][0]=0,\textit{dp}[0][1]=-\textit{prices}[0] dp[0][0]=0,dp[0][1]=−prices[0]。
结束状态
因此,我们只要从前往后依次计算状态即可。由于全部交易结束后,持有股票的收益一定低于不持有股票的收益,因此这时候 dp [ n − 1 ] [ 0 ] \textit{dp}[n-1][0] dp[n−1][0]的收益必然是大于 dp [ n − 1 ] [ 1 ] \textit{dp}[n-1][1] dp[n−1][1] 的,最后的答案即为 dp [ n − 1 ] [ 0 ] \textit{dp}[n-1][0] dp[n−1][0]。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int dp[n][2];
dp[0][0] = 0, dp[0][1] = -prices[0];
for (int i = 1; i < n; ++i) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[n - 1][0];
}
};
注意到上面的状态转移方程中,每一天的状态只与前一天的状态有关,而与更早的状态都无关,因此我们不必存储这些无关的状态,只需要将 dp [ i − 1 ] [ 0 ] \textit{dp}[i-1][0] dp[i−1][0] 和 dp [ i − 1 ] [ 1 ] \textit{dp}[i-1][1] dp[i−1][1] 存放在两个变量中,通过它们计算出 dp [ i ] [ 0 ] \textit{dp}[i][0] dp[i][0] 和 dp [ i ] [ 1 ] \textit{dp}[i][1] dp[i][1] 并存回对应的变量,以便于第 i+1 天的状态转移即可。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int dp0 = 0, dp1 = -prices[0];
for (int i = 1; i < n; ++i) {
int newDp0 = max(dp0, dp1 + prices[i]);
int newDp1 = max(dp1, dp0 - prices[i]);
dp0 = newDp0;
dp1 = newDp1;
}
return dp0;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n 为数组的长度。一共有 2n 个状态,每次状态转移的时间复杂度为 O(1),因此时间复杂度为 O ( 2 n ) = O ( n ) O(2n)=O(n) O(2n)=O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。我们需要开辟 O(n) 空间存储动态规划中的所有状态。如果使用空间优化,空间复杂度可以优化至 O(1)。
二、其实超简单
利润就是卖出大于买入,那么只要获取所有的上升区间就行。
public int maxProfit(int[] arr) {
if (arr == null || arr.length <= 1) return 0;
int ans = 0;
for (int i = 1; i < arr.length; i++) {
if (arr[i] > arr[i-1]) { // 卖出有利可图
ans += (arr[i] - arr[i-1]);
}
}
return ans;
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/solution/mai-mai-gu-piao-de-zui-jia-shi-ji-ii-by-leetcode-s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。