题目:Best Time to Buy and Sell Stock
给定一个数组,数组中一个元素表示一天的股价,求一次交易能得到的最大收益。
思路:
数组可能是多个升序降序组成,只要能找到一组极值,使它们的差最大就可以了。
这样实际上就是每当找到一个极大值,就判断此时的到的差值是否比记录的最大值大,是则替换;
而当找一个极小值,就判断它是否小于记录的极小值,是则替换。
注意:这组极值不一定是最值,因为最大值可能在最小值的前面。
int LeetCode::maxProfit(vector<int>& prices){ if (!prices.size())return 0; int start = prices.at(0), end = 0; int max = 0; for (size_t i = 1; i < prices.size(); i++){ if (prices.at(i) < prices.at(i - 1)){//降序 end = prices.at(i - 1); if (end - start > max)max = end - start;//计算上一次升序的利益值,如果大于当前最大值,则更新最大值 if (prices.at(i) < start)start = prices.at(i);//比较下降是的值是否小于记录的起始值,是则更新起始值 } } if (prices.at(prices.size() - 1) - start > max)max = prices.at(prices.size() - 1) - start; return max; }
题目:Best Time to Buy and Sell StockII
相对于上一题,此道题没有限制交易次数,也就是说可以交易无数次,但是买之前,必须要全部卖完;
思路:
找到所有升序的范围,将它们的差值加起来,就能得到最大利益;
和上面思路类似,只是在比较最大值的地方改为累加起来,同时要更新记录的极小值。
int LeetCode::maxProfit2(vector<int>& prices){ if (!prices.size())return 0; int start = prices.at(0), end = 0; int max = 0; for (size_t i = 1; i < prices.size(); i++){ if (prices.at(i) < prices.at(i - 1)){//降序 end = prices.at(i - 1); if (end - start > 0){ max += end - start;//计算上一次升序的利益值,如果大于当前最大值,则更新最大值 start = prices.at(i); } if (prices.at(i) < start)start = prices.at(i);//比较下降是的值是否小于记录的起始值,是则更新起始值 } } if (prices.at(prices.size() - 1) - start > 0)max += prices.at(prices.size() - 1) - start; return max; }
题目:Best Time to Buy and Sell StockIII
这次是限制交易次数为两次,其他的相同。
思路1:
因为次数是固定两次,因此情况是有限的且可以确定的;因此可以分情况讨论:
设min为所有天数中最小的价格,其对应的天数为mini;max为所有天数中最大的价格,其对应的天数为maxi。
当mini < maxi时,已经会有一个最大收益为max - min,只需找到0-mini,maxi-size之间的最大差值和mini-maxi之间的最大反向差值(先大后小)中差值大的,加上max-min 就是最终的最大收益。
当mini > maxi时,先找到0-maxi,mini-size之间的最大差值,在在maxi-mini之间递归按上面的两种情况求最大收益的对应最大和次大的获益值(不能把和返回)。
数学公式化就是下面这样:(maxProfit是一个函数,下面第二个等式是表示递归,profit是前面第一道题目中的求解方法可以求出来)
maxProfit = max - min + max{profit(0-mini),profit(mini-maxi),profit(maxi-size)}(mini < maxi)
maxProfit = max{maxProfit(0-mini),maxProfit(mini-maxi),maxProfit(maxi-size)}(maxi < mini)
pair<int, int> LeetCode::maxProfit3(vector<int>& prices, int i, int j){ if (j - i < 2)return{0,0}; int mini = i, maxi = i; int min = prices.at(i), max = prices.at(i); for (int k = i + 1; k < j; ++k){ if (prices.at(k) > prices.at(maxi)){ maxi = k; } else if (prices.at(k) < prices.at(mini)){ mini = k; } } max = prices.at(maxi); min = prices.at(mini); if (mini < maxi){ //sndPro第二大的最大获利值,start起始价格 int sndPro = 0,start = prices.at(i); for (int k = i + 1; k <= mini; ++k){//结束点是整个数组的最小值,此时必定是下降的 if (prices.at(k) < prices.at(k - 1)){//降序时 //前面升序中获益比记录的最大值还大则,更新最大值 if (prices.at(k - 1) - start > sndPro)sndPro = prices.at(k - 1) - start; if (prices.at(k) < start)start = prices.at(k);//如果下降后的价格比记录的最小价格还小,则更新最小价格 } } start = prices.at(mini); for (int k = mini + 1; k <= maxi; ++k){//最大反向差;结束点是整个数组的最大值,此时必定是上升的 if (prices.at(k)> prices.at(k - 1)){//升序时 //下降的差值大于记录的第二大最大差值时,更新它 if (start - prices.at(k - 1) > sndPro)sndPro = start - prices.at(k - 1); if (prices.at(k) > start)start = prices.at(k);//升序时的价格大于记录的最大价格时更新它 } } start = prices.at(maxi); for (int k = maxi + 1; k < j; ++k){ if (prices.at(k) < prices.at(k - 1)){//降序时 //前面升序中获益比记录的最大值还大则,更新最大值 if (prices.at(k - 1) - start > sndPro)sndPro = prices.at(k - 1) - start; if (prices.at(k) < start)start = prices.at(k);//如果下降后的价格比记录的最小价格还小,则更新最小价格 } } //可能最后是升序结束没有更新最后一次的获利 if (prices.at(j - 1) - start > sndPro)sndPro = prices.at(j - 1) - start; return{ max - min, sndPro }; } else{ //first是第一大,second是第二大 pair<int, int>p1 = maxProfit3(prices, i, maxi + 1); pair<int, int>p2 = maxProfit3(prices, maxi + 1, mini); pair<int, int>p3 = maxProfit3(prices, mini, j); if (p1.first < p2.first){//合并最大值和第二大值 if (p2.second > p1.first)p1.second = p2.second; else p1.second = p1.first; p1.first = p2.first; } else if (p2.first > p1.second)p1.second = p2.first; if (p1.first < p3.first){ if (p3.second > p1.first)p1.second = p3.second; else p1.second = p1.first; p1.first = p3.first; } else if (p3.first > p1.second)p1.second = p3.first; return p1; } return{ 0, 0 }; }
思路2:
分类讨论情况还是很复杂的,很容易出错;而这类题目是可以使用动态规划来解决的。
P(k,i)表示到第i天的价格时,做了k次交易时的获利。
动态规划:P(k,i) = max{P(k,i-1),max{P(k-1,j) + pi - pj}(0 <= j <= i -1)};
= max{P(k,i-1),pi + max{P(k-1,j) - pj}(0 <= j <= i -1)};
可以设temp = max{P(k-1,j) - pj}(0 <= j <= i -1);
每次更新和P(k,i)一起更新temp,使得复杂度为O(n)。
初始条件:
P(0,i) = 0;还没有做任何交易,没有获利
P(k,0) = 0;第一天的获利必然为0
int LeetCode::maxProfit3(vector<int>& prices){ if (prices.size() < 2)return 0; vector<vector<int>>arr(2,vector<int>(prices.size(),0));//k为2 int maxPro = 0; //temp0交易一次的temp;temp1交易2次的temp int temp0 = -prices.at(0),temp1 = arr.at(0).at(0) - prices.at(0); for (size_t i = 1; i < prices.size(); i++){ arr.at(0).at(i) = max(arr.at(0).at(i - 1), prices.at(i) + temp0);//求P(0,i) temp0 = max(temp0, -prices.at(i));//更新temp0 arr.at(1).at(i) = max(arr.at(1).at(i - 1), prices.at(i) + temp1);//求P(1,i) temp1 = max(temp1, arr.at(0).at(i) - prices.at(i));//更新temp1 maxPro = max(maxPro, arr.at(1).at(i));//求最大值 } return maxPro; }
题目:Best Time to Buy and Sell StockIV
这题不再是固定的两次,而是k次,其他相同;
很显然不能再分类讨论了,那么只能使用动态规划;
思路1:
做法和上面的类似,只是2变成了k,空间复杂度变成了O(kn);
int LeetCode::maxProfit4(int k, vector<int>& prices){ int len = prices.size(); if (len < 2) return 0; if (k > len / 2){//能交易次数在天数的一半以上,则表示可以随意交易,等同于maxProfit2的算法 int ans = 0; for (int i = 1; i<len; ++i){ ans += max(prices[i] - prices[i - 1], 0);//避开小于0的情况 } return ans; } vector<vector<int>>arr(k + 1, vector<int>(prices.size(), 0)); int maxPro = 0; vector<int> temp(k + 1, -prices.at(0)); for (size_t i = 1; i < prices.size(); i++){ for (int j = 1; j <= k; ++j){ arr.at(j).at(i) = max(arr.at(j).at(i - 1), temp.at(j) + prices.at(i));//求P(k,i) temp.at(j) = max(temp.at(j), arr.at(j - 1).at(i) - prices.at(i));//求temp(k) } maxPro = max(maxPro, arr.at(k).at(i));//求最大值 } return maxPro; }
思路2:
从上面的程序可以看出来,其实最终的最大利益值只与前一个的最大利益值和k相关,这样的话应该不需要记录所有的k个最大利益值。
但是具体该怎么做呢?
上面的每天都对应数组的一项,合并成n天公用一项;即是vector<vector<int>>arr(kn);=> vector<int>arr(k);将arr.at(k).at(0-n)合并为arr.at(k)
int LeetCode::maxProfit4(vector<int>& prices, int k){ int len = prices.size(); if (len < 2) return 0; if (k > len / 2){//能交易次数在天数的一半以上,则表示可以随意交易,等同于maxProfit2的算法 int ans = 0; for (int i = 1; i<len; ++i){ ans += max(prices[i] - prices[i - 1], 0);//避开小于0的情况 } return ans; } vector<int>arr(k + 1,0);//记录当前价值下的0 - k个交易后的最大收益值 vector<int> temp(k + 1, -prices.at(0));//temp的初值 for (size_t i = 1; i < prices.size(); i++){ int cur = prices.at(i); for (int j = 1; j <= k; ++j){ arr.at(j) = max(arr.at(j), temp.at(j) + cur);//求P(k,i) temp.at(j) = max(temp.at(j), arr.at(j - 1) - cur);//求temp(k) } } return arr.at(k); }