给定一个数组 prices
,其中 prices[i]
是一支给定股票第 i
天的价格。
设计一个算法来计算所能获取的最大利润。可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:不能同时参与多笔交易(必须在再次购买前出售掉之前的股票)。
示例 1:
输入: prices = [7,1,5,3,6,4] 输出: 7 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: prices = [1,2,3,4,5] 输出: 4 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: prices = [7,6,4,3,1] 输出: 0 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 3 * 10^4
0 <= prices[i] <= 10^4
方法一:贪心算法
1、股票买卖策略
- 单独交易: 设今天价格为 p1,明天的价格为 p2。今天买入明天卖出可得的利润为 profit = p2 - p1(该结果正为收益,负代表亏损)。
- 连续上升交易:设此上升交易的股票价格分别为 p1,p2...pn,则第一天买入第 n 天卖出的利润最大,为 profit = pn - p1。该过程等价于 pn - p1 = (p2-p1) + (p3-p2) + ... + (pn - pn-1),可以看作第一天买入第二天卖出又买入,即每天都在买卖股票。
- 连续下降交易:该情况不买入卖出,利润最大,即不会有亏损。
2、算法流程
- 遍历整个股票交易日价格列表 prices,所有上升交易日都买卖(赚到所有利润),所有下降交易日都不买卖(永不亏钱)。
- 设 temp 为第 i-1 日买入、第 i 日卖出的利润,即 temp = prices[i] - prices[i - 1] ;
- 当该天利润为正0,则将利润加入总利润 profit;当利润为 0 或为负,则跳过;
- 遍历完成,返回总利润 profit。
3、复杂度分析:
- 时间复杂度: O(n), 只需遍历一次prices;
- 空间复杂度: O(1), 变量使用常数额外空间。
class Solution {
public int maxProfit(int[] prices) {
// 贪心算法
int maxprofit = 0;
for(int i = 1; i < prices.length; i ++){
int temp = prices[i] - prices[i - 1];
if(temp > 0){
maxprofit = maxprofit + temp;
}
}
return maxprofit;
}
}
方法二:动态规划
由于「不能同时参与多笔交易」,因此每天交易结束后只可能存在两种情况:手中持有一支股票、手中没有持股 的状态。
定义状态 dp[i][0] 表示第 i 天交易完后手里没有股票的最大利润,dp[i][1] 表示第 i 天交易完后手中持有一支股票的最大利润(i 从 0 开始)。
1、考虑 dp[i][0] 的转移方程,如果这一天交易完后手里没有股票,那么可能的转移状态为:前一天已经没有股票,即 dp[i−1][0],或者前一天结束的时候手中持有一支股票,即 dp[i−1][1],现在要将其卖出,并获得 prices[i] 的收益。因此为了收益最大化,列出如下的转移方程:
dp[i][0]=max{dp[i−1][0], dp[i−1][1]+prices[i]}2、考虑 dp[i][1] 的转移方程,那么可能的转移状态为:前一天已经持有一支股票,即 dp[i−1][1],或者前一天结束时还没有股票,即 dp[i−1][0],现在要买入当天的股票,并减少 prices[i] 的收益。可以列出如下的转移方程:
dp[i][1]=max{dp[i−1][1], dp[i−1][0]−prices[i]}3、对于初始状态,根据状态定义可以知道第 0 天交易结束的时候 dp[0][0]=0,dp[0][1] =− prices[0]。只要从前往后依次计算状态即可。
4、当全部交易结束后,持有股票的收益一定低于不持有股票的收益,因此这时候 dp[n−1][0] 的收益必然是大于 dp[n−1][1] 的,最后的答案即为 dp[n−1][0],返回结果。
优化空间复杂度
对上述的过程进行分析,可以发现当前时间的状态只与前一天的状态有关系,因此无需使用空间存储无关状态,将前一天的 未持股 dp[i-1][0] 与 持股 dp[i-1][1] 存放在两个变量中,通过两者计算出当前时间的结果并更新其值,以便继续后续的结果。
- 复杂度分析:
时间复杂度:O(n),其中 n 为数组的长度。一共有 2n 个状态,每次状态转移的时间复杂度为 O(1),因此时间复杂度为 O(2n)=O(n)。
空间复杂度:O(n)。我们需要开辟 O(n) 空间存储动态规划中的所有状态。如果使用空间优化,空间复杂度可以优化至 O(1)。
class Solution {
public int maxProfit(int[] prices) {
// 动态规划算法
// 存放持股与未持股的利润结果
int[][] dp = new int [prices.length][2];
// 初始化第0天 未持股利润0,持股即买入则利润为负
dp[0][0] = 0;
dp[0][1] = -prices[0];
for(int i = 1; i < prices.length; 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[prices.length - 1][0];
}
}
class Solution {
public int maxProfit(int[] prices) {
// 动态规划算法——优化空间复杂度
// 初始化第0天 未持股利润0,持股即买入则利润为负
// 不断更新
int noStockPro = 0;
int stockPro = -prices[0];
for(int i = 1; i < prices.length; i ++){
int tempNoStock = Math.max(noStockPro, stockPro + prices[i]);
int tempStock = Math.max(stockPro, noStockPro - prices[i]);
noStockPro = tempNoStock;
stockPro = tempStock;
}
return noStockPro;
}
}