使用暴力法,超时了
class Solution {
public:
int maxProfit(vector<int>& prices) {
//暴力求解
int res = 0;
for(int i = 0; i < prices.size(); i++) {//卖的位置
for(int j = 0; j < i; j++) {//买的位置
res = max(res, prices[i] - prices[j]);
}
}
return res;
}
};
第一遍代码审错题了,以为可以多次买进卖出,就直接用贪心写了,就是下一题的贪心思路
只要之后一天的价格高就买入,在买入的情况下,之后的一天价格低就卖出
int buy = -1;
只要没持有就是-1,记录买的下标
int sell = 0;
只要持有就是-1,还没卖,记录卖的下标,初始的时候还没持有,就不应该是-1
class Solution {
public:
int maxProfit(vector<int>& prices) {
//只要之后一天的价格高就买入,在买入的情况下,之后的一天价格低就卖出
int res = 0;
int buy = -1;//只要没持有就是-1,记录买的下标
int sell = 0;//只要持有就是-1,还没卖,记录卖的下标,初始的时候还没持有,就不应该是-1
for(int i = 1; i < prices.size(); i++) {
if(buy == -1 && prices[i-1] < prices[i]) {
buy = i - 1;
//cout << "buy:" << buy << endl;
sell = -1;
}
else if(sell == -1 && prices[i-1] > prices[i]) {
sell = i - 1;
//cout << "sell:" << sell << endl;
res += prices[sell] - prices[buy];
buy = -1;
}
}
if(sell == -1) {
res += prices[prices.size() - 1] - prices[buy];
}
return res;
}
};
代码随想录思路:
贪心的思路其实是可行的,因为股票就买卖一次,那么贪心的想法很自然就是循环遍历每个元素,然后取最左最小值,那么得到的差值就是最大利润
这样就只循环了一次
对第一遍贪心的代码进行修改
class Solution {
public:
int maxProfit(vector<int>& prices) {
//贪心求解
int res = 0;
int buy = prices[0];//记录买的价格,即取左边最小值
for(int i = 1; i < prices.size(); i++) {//卖的位置
buy = min(buy, prices[i - 1]);
res = max(res, prices[i] - buy);
}
return res;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
自己没想到动态规划的思路,代码随想录思路:
动规五部曲分析如下:
1、确定dp数组(dp table)以及下标的含义
dp[i][0] 表示第i天持有股票所得最多现金 ,这里可能有疑惑,本题中只能买卖一次,持有股票之后哪还有现金呢?其实一开始现金是0,那么加入第i天买入股票现金就是 -prices[i], 这是一个负数
dp[i][1] 表示第i天不持有股票所得最多现金(思路跟 leetcode 337 打家劫舍:树形动规 的dp数组很像,因为买要取最大值,卖要取最大值左边最小值 逻辑不一样 不好一起处理 所以对买卖进行分开记录 在一个数组里)
注意这里说的是“持有”,“持有”不代表就是当天“买入”!也有可能是昨天就买入了,今天保持持有的状态
2、确定递推公式
如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来
1、第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
2、第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]
那么dp[i][0]应该选所得现金最大的,即选价钱低的时候 买入,所以dp[i][0] = max(dp[i - 1][0], -prices[i]);
如果第i天不持有股票即dp[i][1], 也可以由两个状态推出来
1、第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
2、第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1][0]
同样dp[i][1]取最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
这样递推公式我们就分析完了
3、dp数组如何初始化
由递推公式 dp[i][0] = max(dp[i - 1][0], -prices[i]);
和 dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
可以看出
其基础都是要从dp[0][0]和dp[0][1]推导出来
那么dp[0][0]表示第0天持有股票,此时的持有股票就一定是买入股票了,因为不可能有前一天推出来,所以dp[0][0] -= prices[0];
dp[0][1]表示第0天不持有股票,不持有股票那么现金就是0,所以dp[0][1] = 0;
4、确定遍历顺序
从递推公式可以看出dp[i]都是由dp[i - 1]推导出来的,那么一定是从前向后遍历
5、举例推导dp数组
以示例1,输入:[7,1,5,3,6,4]为例,dp数组状态如下:
dp[5][1]就是最终结果
为什么不是dp[5][0]呢?
因为本题中不持有股票状态所得金钱一定比持有股票状态得到的多(持有股票的时候都是负的)
根据思路实现代码:
dp[i][0] = max(dp[i - 1][0], -prices[i]);
最大的负数 绝对值最小,所以是max
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<vector<int>> dp(prices.size(), vector<int>(2, 0));
dp[0][0] = -prices[0];//持有
dp[0][1] = 0;//不持有
for(int i = 1; i < prices.size(); i++) {
dp[i][0] = max(dp[i - 1][0], -prices[i]);//最大的负数 绝对值最小,所以是max
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
}
return dp[prices.size() - 1][1];
}
};
从递推公式可以看出,**dp[i]只是依赖于dp[i - 1]**的状态
dp[i][0] = max(dp[i - 1][0], -prices[i]);
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
我们只需要记录 当前天的dp状态和前一天的dp状态就可以了,可以使用滚动数组来节省空间,滚动数组不是说一定能降维,类似某个维度取余减少空间也是滚动数组,这里只依赖两个状态,所以第一个维度整个大小为2的即可
代码随想录代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(2, vector<int>(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]);
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
}
return dp[(len - 1) % 2][1];
}
};
根据思路自己写代码:
以为只依靠前一个数,所以完全可以降维,原来那个存在相同位置的数就是前一个数
要使用dp[i - 1][0],但此时dp[0]已经被覆盖了,使用tmp记录int tmp = dp[0];
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<int> dp(2);
dp[0] = -prices[0];//持有
dp[1] = 0;//不持有
for(int i = 1; i < prices.size(); i++) {
int tmp = dp[0];
dp[0] = max(dp[0], -prices[i]);//最大的负数 绝对值最小,所以是max
//要使用dp[i - 1][0],但此时dp[0]已经被覆盖了,使用tmp记录
dp[1] = max(dp[1], prices[i] + tmp);
}
return dp[1];
}
};
时间复杂度:O(n)
空间复杂度:O(1)
之前使用贪心解决过这道题:贪心:对利润的每日分解(122)
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int startIndex = -1;//用-1表示没找到买的那天
int endIndex = -1;//没找到卖的那天
int result = 0;
for(int i = 0; i < prices.size() - 1; i++) {
if(prices[i] < prices[i + 1]) {
if(startIndex == -1) {//买之前要没买
startIndex = i;
endIndex = -1;//还没卖
}
}
else {
if(startIndex != -1) {//卖之前要一定买
endIndex = i;
result += prices[endIndex] - prices[startIndex];
startIndex = -1;//卖掉了,需要重新确定买的那天
}
}
}
//处理到数列末尾 单调递增(直接不卖显然不合理),单调递减(直接就是0不用特别处理,但是如果是整个数列单调递减的,注意startindex为-1,不能进入if)
if(endIndex == -1 && startIndex != -1) {
result += prices[prices.size() - 1] - prices[startIndex];
}
return result;
}
};
动态规划没思路,代码随想录动态规划思路:
本题和 121. 买卖股票的最佳时机 的唯一区别是本题股票可以买卖多次了(注意只有一只股票,所以再次购买前要出售掉之前的股票)
在动规五部曲中,这个区别主要是体现在递推公式上,其他都和 121. 买卖股票的最佳时机 一样
所以我们重点讲一讲递推公式
这里重申一下dp数组的含义:
dp[i][0] 表示第i天持有股票所得现金
dp[i][1] 表示第i天不持有股票所得最多现金
如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来
第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0](只要不卖 跟可以买多少次没有关系,所以没有变化)
第i天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i](买了因为可以多次购买,就出现不同了,而且只要dp[i - 1][1]为正就是实打实比只能买卖一次 多赚到的钱)
注意这里和121. 买卖股票的最佳时机 唯一不同的地方,就是推导dp[i][0]的时候,第i天买入股票的情况
在 121. 买卖股票的最佳时机 中,因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]
而本题,因为一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润
那么第i天持有股票即dp[i][0],如果是第i天买入股票,所得现金就是昨天不持有股票的所得现金 减去 今天的股票价格 即:dp[i - 1][1] - prices[i]
再来看看如果第i天不持有股票即dp[i][1]的情况, 依然可以由两个状态推出来
第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1][0]
注意这里和121. 买卖股票的最佳时机 就是一样的逻辑,卖出股票收获利润(可能是负值)并不受可以买多次的影响
代码如下:(注意代码中的注释,标记了和121.买卖股票的最佳时机唯一不同的地方)
这正是因为本题的股票可以买卖多次,所以买入股票的时候,可能会有之前买卖的利润即:dp[i - 1][1],所以dp[i - 1][1] - prices[i]
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(len, vector<int>(2, 0));
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); // 注意这里是和121. 买卖股票的最佳时机唯一不同的地方。
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
}
return dp[len - 1][1];
}
};
根据思路实现一维数组的代码,降维方法与上一题相同:
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<int> dp(2);
dp[0] = -prices[0];
dp[1] = 0;
for(int i = 1; i < prices.size(); i++) {
int tmp = dp[0];
dp[0] = max(dp[0], dp[1] - prices[i]);
dp[1] = max(dp[1], tmp + prices[i]);
}
return dp[1];
}
};
代码随想录滚动数组的版本代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(2, vector<int>(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]);
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
}
return dp[(len - 1) % 2][1];
}
};
时间复杂度:O(n)
空间复杂度:O(1)