LeetCode Best Time to Buy and Sell Stock I-II-III-IV
买股票,动态规划。
LeetCode Best Time to Buy and Sell Stock I
题目描述
链接:https://leetcode.com/problems/best-time-to-buy-and-sell-stock/
给出股票每一天的价格,问只允许交易一次可以获得的最大利润。
输入每一天的股价vector<int> prices
,返回最大利润。
算法
因为只允许交易一次,所以可以顺序遍历一遍,对每一段连续不减区间求最大利润。
或者,dp
解法,
dp[j]
表示前j天可以获得的最大的利润。转移为dp[j]=max(dp[j-1], prices[j]-minBuy)
代码
dp实现。
int maxProfit(vector<int>& prices) { int minPrice = inf, n = prices.size(), ret = 0; for (int i = 0; i < n; i++) { ret = max(ret, prices[i]-minPrice); minPrice = min(prices[i], minPrice); } return ret; }
LeetCode Best Time to Buy and Sell Stock II
题目描述
链接:https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/
给出股票每一天的价格,允许任意次交易,且在买入之前必须卖掉手中的股票。求可以获得的最大利润。注意,一天可以先卖掉手中的再买入。
输入每一天的股价vector<int> prices
,返回最大利润。
算法
允许任意次交易,所以,只需要对每一个递增区间求最大利润即可。
代码
int maxProfit(vector<int>& prices) { int ret = 0, n = prices.size(); for (int i = 1; i < n; i++) { if (prices[i] > prices[i-1]) { ret += prices[i] - prices[i-1]; } } return ret; }
LeetCode Best Time to Buy and Sell Stock III
题目描述
链接:https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/
给出股票每一天的价格,只允许最多两次交易,且在买入之前必须卖掉手中的股票。求可以获得的最大利润。注意,一天可以先卖掉手中的再买入。
输入每一天的股价vector<int> prices
,返回最大利润。 帮小白在线笔试某团购网站X团的笔试题,所以刷LeetCode还是有帮助的。
算法
这题稍微有了一些难度。但是,由问题I的dp思路,f[j]
表示表示前j天最多交易一次可以获得的最大的利润,如果可以知道第j天到第n天最多交易一次可以获得的最大的利润b[j]
,那么就可以遍历n天,max(f[j]+b[j])
即为结果。
所以,我们可以dp两次,一次从前向后,f[j]表示前j天最多交易一次可以获得的最大的利润,f[j]=max(f[j-1], prices[j]-minBuy)
。
另一次从后向前,b[j]表示j到n天最多交易一次可以获得的最大的利润,b[j]=max(b[j+1], maxSell-prices[j])
。
max(f[j]+b[j])
为最后的结果。
代码
int maxProfit(vector<int>& prices) { int n = prices.size(), minBuy, maxSell; if (n == 0 || n == 1) return 0; vector<int> f, b; f.resize(n); b.resize(n); f[0] = 0; b[n-1] = 0; minBuy = prices[0]; maxSell = prices[n-1]; for (int i = 1; i < n; i++) { f[i] = max(f[i-1], prices[i]-minBuy); minBuy = min(minBuy, prices[i]); } for (int i = n-2; i >= 0; i--) { b[i] = max(b[i+1], maxSell - prices[i]); maxSell = max(maxSell, prices[i]); } int ret = 0; for (int i = 0; i < n; i++) { ret = max(ret, f[i]+b[i]); } return ret; }
LeetCode Best Time to Buy and Sell Stock IV
题目描述
链接:https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/
给出股票每一天的价格,只允许最多k次交易,且在买入之前必须卖掉手中的股票。求可以获得的最大利润。注意,一天可以先卖掉手中的再买入。
输入k
次交易数,每一天的股价vector<int> prices
,返回最大利润。
算法
这题一开始想了一个二维dp,dp[i][j]表示前i天进行j次交易,然后一个二维数组last[i][j]记录dp[i][j]最后一次交易的第k,维护一个单调递增队列,为了可以快速找到第k天到当前最小的价格。然后滚动数组优化下内存,但是还是MLE。大概代码如下,不想看就果断略过。
for (int j = 1; j <= k; j++) { front = 0; rear = 0; queue[rear] = 0; rear++; for (int i = 1; i < n; i++) { int l = last[(i-1)&1][j - 1]; while (front < rear && queue[front] < l + 1) { front += 1; } int curMin = prices[queue[front]]; if (dp[(i-1)&1][j] > dp[(i-1)&1][j - 1] + prices[i] - curMin) { dp[i&1][j] = dp[(i-1)&1][j]; last[i&1][j] = last[(i-1)&1][j]; } else { dp[i&1][j] = dp[(i-1)&1][j - 1] + prices[i] - curMin; last[i&1][j] = i; } while (front < rear && prices[queue[rear - 1]] >= prices[i]) { rear -= 1; } queue[rear] = i; rear++; } }
网上找到了正解。想一下,问题的本质也就是,在一个n个数中,找出2k个序列,奇数买入操作,获得利润-prices[j],偶数卖出操作,获得利润prices[j]。
所以二维dp状态,dp[i][j]
表示前i天进行j次操作产生的最大利润,有转移dp[i][j]=max(dp[i-1][j], dp[i-1][j-1] + prices[i] * (j&1 : -1 : 1))
。
初始化,dp[0][0]=0, dp[0][j]=-inf (j=0···n)
,即第0天0次交易利润为0,而dp[0][j]为不可达状态,置为-inf。
同时,我们可以注意到dp[i][j],第一维可以省掉,即dp[j]=max(dp[j], dp[j-1] + prices[i] * (j&1 : -1 : 1))
。
此外,还有一个小优化,当k很大,以至于2k >= n,也就是n天可以进行任意次数操作,所以变成了问题II,直接采用问题II的算法。
代码
class Solution { private: int maxProfit(vector<int>& prices) { int ret = 0, n = prices.size(); for (int i = 1; i < n; i++) { if (prices[i] > prices[i-1]) { ret += prices[i] - prices[i-1]; } } return ret; } public: int maxProfit(int k, vector<int>& prices) { int n = prices.size(); if (2*k >= n) { return maxProfit(prices); } vector<int> dp; dp.resize(2*k+1); dp[0] = 0; for (int i = 1; i <= 2*k; i++) dp[i] = -inf; for (int i = 0; i < n; i++) { for (int j = 1; j <= min(2*k, i+1); j++) { dp[j] = max(dp[j], dp[j-1] + prices[i] * ( j&1 ? -1 : 1)); } } return dp[2*k]; } };
goudan-er SHARE · ALGORITHM
LeetCode Algorithm