您的位置:首页 > 其它

每天一道LeetCode-----买卖商品问题,计算最大利润,分别有一次交易,两次交易,多次交易的情况

2018-01-10 14:37 681 查看

Best Time to Buy and Sell Stock

原题链接Best Time to Buy and Sell Stock



给定一个价格序列prices,其中prices[i]代表第i天商品的价格,商家需要在某一天买入,然后在之后的某一天出售,计算可以获得的最大利润

本质就是计算prices[i]−prices[j]的最大值,要求i>j

假设已经直到最后结果的最大值位置i(prices[i]),只需要减去最小值即可,那么就需要直到最小值是多少,本题中当从左到右遍历时,完全可以随着遍历进度的增加记录最小值

代码如下

class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.empty())  return 0;
/* 记录目前位置找到的最小值 */
int minPrices = prices[0];
/* 记录最大差值 */
int maxProfit = 0;
for(int i = 1; i < prices.size(); ++i)
{
/* 以prices[i]作为最大值,尝试计算差值 */
maxProfit = std::max(maxProfit, prices[i] - minPrices);
/* 尝试更新最小值 */
minPrices = std::min(minPrices, prices[i]);
}
return maxProfit;
}
};




当然也可以遍历两次求解,第一次对于每个位置的值,计算它的右侧比它大的最大值,第二次对于每个位置,计算右侧的最大值与自己的差,更新最大差值,求出结果

代码如下

class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<int> dp(n + 1, 0);
/* 计算prices[i]右侧的最大值,包括自己 */
for(int i = n - 1; i >= 0; --i)
dp[i] = std::max(dp[i + 1], prices[i]);
int maxProfit = 0;
/* 右侧最大值 - 自身 = 最大差值 */
for(int i = 0; i < n; ++i)
maxProfit = std::max(maxProfit, dp[i] - prices[i]);
return maxProfit;
}
};


Best Time to Buy and Sell Stock II

原题链接Best Time to Buy and Sell Stock II



要求和上面一样,不过这次可以进行多次交易,即买,卖,买,卖,买,卖…,其中买卖不能在同一天进行

这样就不能像上面那样只求一次了,其实仔细想一下,序列无非三种排列

递增,假设A<B<C<D

递减,假设A>B>C>D

无序,假设A<B>C<D

对于递增序列,最大的差值就是D−A,因为(D−A)=(D−C)+(C−B)+(B−A)>(D−C)+(B−A)

对于递减序列,为0

对于无序序列,总可以找到若干个递增序列,就上面的例子而言最大差值为(B−A)+(D−C),而实际上也可以写成(D−C)+max(C−B,0)+(B−A),和递增序列的形式是一样的

所以只要依次计算prices[i]−prices[i−1]即可

代码如下

class Solution {
public:
int maxProfit(vector<int>& prices) {
int maxProfit = 0;
for(int i = 1; i < prices.size(); ++i)
maxProfit += std::max(prices[i] - prices[i - 1], 0);
return maxProfit;
}
};


Best Time to Buy and Sell Stock III

原题链接Best Time to Buy and Sell Stock III



同样是买卖问题,本题要求最多可以完成两次交易,计算最大利润

利用动态规划,对动态规划数组states[i][j]有如下规定

states[][0]代表0次买,0次卖的利润

states[][1]代表一次买,0次卖的利润(显然是负数)

states[][2]代表一次买,一次卖的利润

states[][3]代表两次买,一次卖的利润

states[][4]代表两次买,两次卖的利润

而states,规定它是二维数组,states[0]代表当前的利润,states[1]代表执行完本次操作后的利润,那么有

states[1][1] = std::max(states[0][1], states[0][0] - prices[i]),表示买入商品prices[i],那么利润就是0次买0次卖的利润减商品价格,当然需要和1次买0次卖比较,选择较大的那个

states[1][2] = std::max(states[0][2], states[0][1] + prices[i]),表示卖出商品prices[i],那么利润就是1次买0次卖的利润加商品价格,当然需要和1次买1次卖比较,选择较大的那个

…同理

初始状态,另1次买0次卖的利润为INT_MIN,两次买一次卖的利润为INT_INT,是为了让max选择后者,因为初始状态没有买入任何商品

代码如下

class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<vector<int>> states(2, vector<int>(2 * 2 + 1, 0));
states[0][0] = 0;
for(int i = 1; i < 2 * 2 + 1; i += 2)
{
states[0][i] = INT_MIN;
states[0][i + 1] = 0;
}
int cur = 0, next = 1;
for(int i = 0; i < prices.size(); ++i)
{
states[next][1] = std::max(states[cur][1], states[cur][0] - prices[i]);
states[next][2] = std::max(states[cur][2], states[cur][1] + prices[i]);
states[next][3] = std::max(states[cur][3], states[cur][2] - prices[i]);
states[next][4] = std::max(states[cur][4], states[cur][3] + prices[i]);
/* next变为当前 */
std::swap(next, cur);
}
return states[cur][2 * 2];
}
};


Best Time to Buy and Sell Stock IV

原题链接Best Time to Buy and Sell Stock IV



和上面一样,要求最多可以进行k次交易

只需要将上面的两次交易换成k次即可

class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
vector<vector<int>> states(2, vector<int>(2 * k + 1, 0));
states[0][0] = 0;
for(int i = 1; i < 2 * k + 1; i += 2)
{
states[0][i] = INT_MIN;
states[0][i + 1] = 0;
}
int cur = 0, next = 1;
for(int i = 1; i < prices.size(); ++i)
{
/* 把两次变为k次,就需要放在循环里了 */
for(int j = 1; j < 2 * k + 1; j += 2)
{
states[next][j] = std::max(states[cur][j], states[cur][j - 1] - prices[i]);
states[next][j + 1] = std::max(states[cur][j + 1], states[cur][j] + prices[i]);
}
std::swap(next, cur);
}
return states[cur][2 * k];
}
};


不过这样可能内存溢出,因为如果k很大,申请的内存很容易溢出,解决方法是当k很大时采用其他方法。什么时候k算大呢,就是k次交易可以涉及到每一天,也就是k >= prices.size() / 2,此时就可以转换为没有交易次数限制的问题

代码如下

class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
/* 如果k很大,采用其他方式 */
if(k >= prices.size() / 2)
return quickSolver(prices);
vector<vector<int>> states(2, vector<int>(2 * k + 1, 0));
for(int i = 1; i < 2 * k + 1; i += 2)
{
states[0][i] = INT_MIN;
states[0][i + 1] = 0;
}
int cur = 0, next = 1;
for(int i = 0; i < prices.size(); ++i)
{
/* 把两次变为k次,就需要放在循环里了 */
for(int j = 1; j < 2 * k + 1; j += 2)
{
states[next][j] = std::max(states[cur][j], states[cur][j - 1] - prices[i]);
states[next][j + 1] = std::max(states[cur][j + 1], states[cur][j] + prices[i]);
}
std::swap(next, cur);
}
return states[cur][2 * k];
}
private:
/* 没有交易次数限制,直接遍历,见第二题 */
int quickSolver(vector<int>& prices)
{
int profit = 0;
for(int i = 1; i < prices.size(); ++i)
profit += std::max(prices[i] - prices[i - 1], 0);
return profit;
}
};


第一道题的第一种解法比较好,直接遍历一遍同时记录最小值和最大差值,同时也没有额外的空间使用

第二道题不太容易理解,其实对于序列无非递增,递减,无序三种可能,分别观察一下即可

第三第四题主要以动态规划为主
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  leetcode
相关文章推荐