一、题目[LeetCode-121]
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
- 1 <= prices.length <= 10^5
- 0 <= prices[i] <= 10^4
二、思路
1)动态规划
在《Day2 买卖股票的最佳时机II》的动态规划法中,定义dp[i][0]表示第i天(i从0计)交易完成后手里没有股票的利润,dp[i][1]表示第i天(i从0计)交易完成后手里持有股票的利润。
则有状态转移方程
- dp[i][0] = max( dp[i-1][0], dp[i-1][1] + price[i] )
- dp[i][1] = max( dp[i-1][0] - price[i], dp[i-1][1] )
而本题也同理。然而需要作出改动。本题与《买卖股票的最佳时机II》的区别在于,在后者可以在这几天中买卖股票多次,而在本题只能买卖一次。对于dp[i][0]的状态转移方程dp[i][0] = max( dp[i-1][0], dp[i-1][1] + price[i] ),不用更改。但是对于dp[i][1],因为我们限定 只能购买一次,因此在dp[i][1]之前,如果买了一次,我们在dp[i][1]只能取dp[i-1][1],如果还没卖,那么dp[i][1]就是第一次购买,也是唯一一次购买,因此取-price[i]。
由此,得出dp[i][1] = max( -price[i], dp[i-1][1] )
初始条件:对于第一天,没有股票时,则说明没有购买,dp[0][0] = 0;
持有股票时,则说明恰好第一天购买了,dp[0][1] = -price[0];
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int>> dp(n);
for(int i = 0; i < n; i++)
dp[i] = vector<int>(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(prices[i] * -1, dp[i-1][1]);//由动态规划转移方程构建动态规划数组
}
return dp[n-1][0];//对于最后一天,显然dp[n-1][0]>dp[n-1][1]
}
};
2)动态规划进一步优化
容易看出,对于上一思路构建的动态规划数组,我们只需要最后的dp[n-1][0]和dp[n-1][1],而对于其他的区间段dp[0, n-1)[0]和dp[0, n-1)[1],我们最终不需要,而它们会占用O(n)的存储空间。因此,可以进一步优化,不使用数组的形式实现,而是直接进行迭代!
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int dp0 = 0;
int dp1 = prices[0] * -1;//动态规划转移方程初始条件
for(int i = 1; i < n; i++)
{
dp0 = max(dp0, dp1 + prices[i]);
dp1 = max(prices[i]*-1, dp1);//根据状态转移方程进行n-1次迭代
}
return dp0;//对于最后一天,显然dp0>dp1
}
};
三、官方题解(来源:力扣(LeetCode))
暴力法
我们需要找出给定数组中两个数字之间的最大差值(即,最大利润)。此外,第二个数字(卖出价格)必须大于第一个数字(买入价格)。
形式上,对于每组 i 和 j(其中 j>i)我们需要找出 max(prices[j]−prices[i])。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = (int)prices.size(), ans = 0;
for (int i = 0; i < n; ++i){
for (int j = i + 1; j < n; ++j) {
ans = max(ans, prices[j] - prices[i]);
}
}
return ans;
}
};
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/121-mai-mai-gu-piao-de-zui-jia-shi-ji-by-leetcode-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
复杂度分析
- 时间复杂度:O(n^2)。循环运行n(n-1)/2 次。
- 空间复杂度:O(1)。只使用了常数个变量。
四、学习心得
动态规划题的关键在于在实际问题中找出状态转移方程。如《Day2买卖股票的最佳时机I》和《Day35买卖股票的最佳时机II》的第i天结束后我“持有股票”和“没有股票”两种状态下的总利润与前一天第i-1天的状态的关系、《Day34爬楼梯》的爬至第n阶状态方法数与爬至第n-1阶状态和爬至第n-2阶状态方法数的关系。