第五十六天 --- 力扣122(贪心+线性DP)
题目要求:
整体思路一:贪心算法
抓住一点,记录前序点,只要降价就卖,并且再次买入,只要涨价就等下一天,暂时不卖,就可以了。
具体代码一
class Solution {
public:
int ans = 0;
int pre = -1;//前序节点
int mins = 1 << 30;//最低价
int temp_ans = 0;
int maxProfit(vector<int>& prices) {
for (int i = 0; i < prices.size(); i++) {
if (prices[i] < pre&&pre != -1) {//发生降价
mins = prices[i];
ans += temp_ans;
temp_ans = 0;
}
else if (pre == -1) {
mins = prices[i];//第一个节点要更新最小值
}
else {
temp_ans = prices[i] - mins;//涨价
}
pre = prices[i];//每次更新前序节点
}
ans += temp_ans;//防止最后一次涨价后,少计算一次收益
return ans;
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):
时间复杂度:O(N) :只需要遍历一遍数组即可
整体思路二:DP算法
1、博主在做这个题的时候出现了一个问题,将线性DP,区间DP,背包DP弄混了,咱们先说说咋区分它们:
<1>背包DP:这个最好判断,他最明显的特征是会给出一个限制,比如给0-1背包给一个最大的背包容量,或者采草药问题那种给一个时间限制,在这个限制之上,我们求解最大效益值,这是最明显的特征,其次会给出两组队列,这是第二个特征。有了他们两个特征作为判断,我们就可以较为轻松的判断出来背包问题。0-1背包+完全背包以及优化见我的博客背包问题
<2>线性DP,这种DP最明显的特征是给出一个线性序列,没有限制,但是要求我们在一个线性队列上操作(这个是最重要的特点),一般只要从前到后处理一遍就行了(并不全是这样),这一类的都可以归为线性DP,博主找到了另一位博主总结的经典线性DP合集,线性DP,肥肠滴银杏。
<3>区间DP:我们要在一个区间上求得最优值,区间不一定要以谁为起点,我们仅仅可以确定区间长度,区间的起点终点都不一定是谁,(这里明显的区别于线性DP)不断地由小到大枚举区间长度,大区间由小区间推出来,枚举所有可能的区间详见关路灯+石子合并
(这些只是个人目前的理解,这些只是可以帮我们更快速的确定解题思路,但一定还是要具体问题具体分析!有错误希望大家指出,谢谢)
2、再回到这题,我们很明显发现,给了一个序列,表示股价在每一天是多少,日子只能从前到后的过,每一天面临着是买入股票还是卖出股票,并且只能先买再卖,买入股票花钱,效益值为负,卖出股票效益值为正。
3、故综合上方分析:今天的状态只由昨天决定,明确的线性结构
具体代码2.1
1、这个版本没有空间优化
2、就两个状态,日子从前到后的过,所以第一维度i表示处理到谁了,每一天0没有股票,1持有股票。所以第二维j是否持有股票。
3、初始化时候,第一天占有股票,花钱了,资产为负(-prices[0]),第一天不占有股票,不花钱,资产为0.
4、状态转移:
<1>第i天不占有股票,由第i-1天占有(说明卖了,额外多得 prices[i])或者不占有股票转移而来
<2>第i天占有股票,由第i-1天占有或者不占有(说明买了,额外少得 prices[i])股票转移而来
5、找答案时候就是最后一天占有或者不占有二者取最大值即可。
class Solution {
public:
int num = 0;
int dp[30000][2] = {};//日子一天一天过,每一天我都可以买股票卖股票
int maxProfit(vector<int>& prices) {
num = prices.size();
dp[0][0] = 0;//0代表不持有股票
dp[0][1] = -prices[0];
for (int i = 1; i < num; i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return max(dp[num - 1][0], dp[num - 1][1]);
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):
具体代码2.2
1、开启空间优化:这个就像0-1背包问题似的,起始地一维没啥用,我就是按照线性顺序处理,所以第一维干掉就行,然后存在一个上一个状态数据被覆盖的问题,我们只需要题前拿出来存储一下就行了。
2、别的分析和上面一样。
class Solution {
public:
int num = 0;
int dp[2] = {};//日子一天一天过,每一天我都可以买股票卖股票
int maxProfit(vector<int>& prices) {
num = prices.size();
dp[0] = 0;//0代表不持有股票
dp[1] = -prices[0];
for (int i = 1; i < num; i++) {
int one = dp[0];//老状态题前存
int two = dp[1];
dp[0] = max(one, two + prices[i]);
dp[1] = max(two, one - prices[i]);
}
return max(dp[0], dp[1]);
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):
具体代码2.3(错误版)
我最开始走上了歪路,就想着用区间DP去解,在分析过后,用二维i,j分别表示区间起点终点,并且只能起点买终点卖(根据题意),大的区间由小区间转移而来,在众多小区间中找最值,最后如果能在区间首尾买卖,则再次比较一下就行。
class Solution {//错误版本,数据量太大,这个栈溢出了
public:
int num = 0;
int dp[30000][30000] = {};//这里会爆炸
int maxProfit(vector<int>& prices) {
num = prices.size();
for (int l = 1; l < num; l++) {
for (int i = 0, j; i + l < num; i++) {
j = i + l;
for (int k = i; k < j; k++) {
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j]);//枚举小区间,找到小区间使之和最大
}
if (prices[i] < prices[j]) {
dp[i][j] = max(dp[i][j], prices[j] - prices[i]);//如果区间首尾可以买卖,再次判断
}
}
}
return dp[0][num - 1];
}
};
错误之处:
1、这个题他给的序列只能从前到后,当前状态就是从上一个转移过来,区间DP就会疯狂的枚举一大堆没用的根本用不上的区间,复杂度太高了。
2、数据量太大了,题目测得数据很严格,光开二维数组就栈溢出了。
3、综上它并不适合区间DP。