You are given an array prices
where prices[i]
is the price of a given stock on the ith
day.
You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock.
Return the maximum profit you can achieve from this transaction . If you cannot achieve any profit, return 0
.
Example 1:
Copy Input: prices = [7,1,5,3,6,4]
Output: 5
Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.
Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell.
Example 2:
Copy Input: prices = [7,6,4,3,1]
Output: 0
Explanation: In this case, no transactions are done and the max profit = 0.
Constraints:
1 <= prices.length <= 105
Solution:
Version 1:
I限制了只能买卖一次。于是要尽可能在最低点买入最高点抛出。这里的一个隐含的限制是抛出的时间必须在买入的时间之后。所以找整个数组的最大最小值之差的方法未必有效,因为很可能最大值出现在最小值之前。但是可以利用类似思路,在扫描数组的同时来更新一个当前最小值minPrice。这样能保证当扫到i时,minPrices必然是i之前的最小值。当扫到i时:
如果prices[i] < minPrice,则更新minPrice = prices[i]。并且该天不应该卖出。
如果prices[i] >= minPrice,则该天可能是最好的卖出时间,计算prices[i] - minPrice,并与当前的单笔最大利润比较更新
不要忘记corner case.
Copy public int maxProfit(int[] prices) {
if(prices==null || prices.length==0){
return 0;
}
int maxprofit=Integer.MIN_VALUE;
int minprice=prices[0];
for(int i=1;i<prices.length;i++){
if(prices[i]<minprice){
minprice=prices[i];
}
maxprofit=Math.max(maxprofit,prices[i]-minprice);
}
return maxprofit;
}
Version 2: DP
k = 1
直接套状态转移方程,根据 base case,可以做一些化简:
Copy dp [ i ] [ 1 ][ 0 ] = max (dp[i - 1 ][ 1 ][ 0 ], dp[i - 1 ][ 1 ][ 1 ] + prices[i])
dp [ i ] [ 1 ][ 1 ] = max (dp[i - 1 ][ 1 ][ 1 ], dp[i - 1 ][ 0 ][ 0 ] - prices[i])
= max (dp[i - 1 ][ 1 ][ 1 ], - prices[i])
解释:k = 0 的 base case,所以 dp [ i - 1 ] [ 0 ][ 0 ] = 0 。
现在发现 k 都是 1 ,不会改变,即 k 对状态转移已经没有影响了。
可以进行进一步化简去掉所有 k:
dp [ i ] [ 0 ] = max (dp[i - 1 ][ 0 ], dp[i - 1 ][ 1 ] + prices[i])
dp [ i ] [ 1 ] = max (dp[i - 1 ][ 1 ], - prices[i])
直接写出代码:
Copy int n = prices . length ;
int [][] dp = new int [n][ 2 ];
for ( int i = 0 ; i < n; 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[n - 1 ][ 0 ];
显然 i = 0
时 i - 1
是不合法的索引,这是因为我们没有对 i
的 base case 进行处理,可以这样给一个特化处理:
Copy if (i - 1 == - 1 ) {
dp[i][ 0 ] = 0 ;
// 根据状态转移方程可得:
// dp[i][0]
// = max(dp[-1][0], dp[-1][1] + prices[i])
// = max(0, -infinity + prices[i]) = 0
dp[i][ 1 ] = - prices[i];
// 根据状态转移方程可得:
// dp[i][1]
// = max(dp[-1][1], dp[-1][0] - prices[i])
// = max(-infinity, 0 - prices[i])
// = -prices[i]
continue ;
}
第一题就解决了,但是这样处理 base case 很麻烦,而且注意一下状态转移方程,新状态只和相邻的一个状态有关,其实不用整个 dp
数组,只需要一个变量储存相邻的那个状态就足够了,这样可以把空间复杂度降到 O(1):
Copy // 原始版本
int maxProfit_k_1( int [] prices) {
int n = prices . length ;
int [][] dp = new int [n][ 2 ];
for ( int i = 0 ; i < n; i ++ ) {
if (i - 1 == - 1 ) {
// base case
dp[i][ 0 ] = 0 ;
dp[i][ 1 ] = - prices[i];
continue ;
}
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[n - 1 ][ 0 ];
}
// 空间复杂度优化版本
int maxProfit_k_1( int [] prices) {
int n = prices . length ;
// base case: dp[-1][0] = 0, dp[-1][1] = -infinity
int dp_i_0 = 0 , dp_i_1 = Integer . MIN_VALUE ;
for ( int i = 0 ; i < n; i ++ ) {
// dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp_i_0 = Math . max (dp_i_0 , dp_i_1 + prices[i]);
// dp[i][1] = max(dp[i-1][1], -prices[i])
dp_i_1 = Math . max (dp_i_1 , - prices[i]);
}
return dp_i_0;
}
两种方式都是一样的,不过这种编程方法简洁很多,但是如果没有前面状态转移方程的引导,是肯定看不懂的。后续的题目,你可以对比一下如何把 dp
数组的空间优化掉。