Day51
1.最佳买卖股票时机含冷冻期
1.dp数组的含义:dp[i][0]为第i天卖出股票的最大价值;dp[i][1]为第i天持有股票的最大价值
2.dp数组的条件:由于有冷冻期,所以dp数组的条件就变了。第i天卖出股票的最大价值有两种情况,一是上一次卖出股票的最大价值,二是前一次持有股票现在卖出的最大价值,那么条件就是dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i])。第i天持有股票的最大价值也有两种,一为上一次持有股票的最大价值,二为两天前卖出股票的最大价值(因为冷冻期不能卖上一天持有的股票),那么条件为dp[i][1]=max(dp[i-1][1],dp[i-2][0]-prices[i]);
3.初始化:dp[0][0]=0和dp[0][1]=-prices[0]没什么好说的。但是由于我们的持股逻辑变为i-2,也就是说如果从1开始遍历会出现越界访问,那么我们需要提前设置1位置的值,1位置的逻辑是不需要考虑冷冻期的,所以延续前几题对dp数组条件的理解,自然知道初始化为:dp[1][0]=max(dp[0][0],dp[0][1]+prices[1])和dp[1][1]=max(dp[0][1],dp[0][0]-prices[1]);
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()==1)
return 0;
vector<vector<int>>dp(prices.size(),{0,0});
dp[0][0]=0;
dp[0][1]=-prices[0];
dp[1][0]=max(dp[0][0],dp[0][1]+prices[1]);
dp[1][1]=max(dp[0][1],dp[0][0]-prices[1]);
for(int i=2;i<prices.size();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-2][0]-prices[i]);
}
return dp[prices.size()-1][0];
}
};
2.买卖股票的最佳时机含手续费
犹豫本题加入了所谓的“手续费”,因此其实条件需要在卖出股票的时候需要将手续费一并减去,说白了就是在原来卖出的基础上便宜了一点,但是我们寻找卖出的最大价值随着dp数组的含义不变而不变。数组的含义依然是最大值,只不过可能频繁交易会出现原先频繁买卖的股票方法可能小了,但是dp数组依然是通过每一次的买入卖出得到包含减去手续费的最有价值的方法,那么条件自然只需要减去fee即可,所以变成了dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]-fee)
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
vector<vector<int>>dp(prices.size(),{0,0});
dp[0][0]=0;
dp[0][1]=-prices[0];
for(int i=1;i<prices.size();i++)
{
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]-fee);
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
}
return dp[prices.size()-1][0];
}
};
3.总结
我是觉得股票类问题核心就是在确定d数组到底表示一些什么,应该这么定义能把所有的情况囊括进去。
1.比如最开始的股票问题,只是找最大值,那么我们就不需要考虑需要累加的问题,直接取最大值比较。
2.比如后面的累加最大价值,那么其实就是将上一次的价值和当前卖出的股票价值加上求出最大值,比起最开始的求一次最大值也就多了一步处理逻辑。
3.又限制购买次数,这样我们所设定的dp数组又需要表示第n次持股或者抛股的定义。最后根据上下关系进行条件判断
那么其实都是需要先清楚题目条件,找到适合的dp数组定义,是否多次或者一次的买入。随后看给出的条件对dp数组条件进行模拟,其实模拟的思路很简单,针对单个条件就是看前一次和当前次的关系;针对整体,则是看每层相互的关系。当确定条件后,根据条件进行对应的初始化。初始化最开始的值要满足题目描述和dp定义以及dp数组对应操作规范,最后还需要观察是否需要特殊的初始化。
Day52
1.最长递增子序列
1.每一个数字自己就有1长度,那么初始化就很简单了,直接全部设置为1。
2.dp数组的含义:第i个位置对应数字的最长递增子序列
3.条件:第i位置时,我们要往前看,遍历每一个前面的数,如果前面j位置的数比i位置的小,那么我们其实可以考虑更新,那么更新的思路是找最长,那么需要比较当前长度和前面第j位置的长度+1,自然条件得到为dp[i]=max(dp[i],dp[j]+1)
4.由于我们dp数组存放的是i对应数字的最长递增子序列,而不是整体的最长,我们需要一个ret来记录最长的数,最后返回
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int>dp(nums.size(),1);
int ret = 1;
for(int i=1;i<nums.size();i++)
{
for(int j=i-1;j>=0;j--)
{
if(nums[i]>nums[j])
{
dp[i]=max(dp[i],dp[j]+1);
}
}
ret=max(ret,dp[i]);
}
return ret;
}
};
2.最长连续递增序列
本题其实就是比较当前位置的数和前一个位置对应的数的大小,如果大于前一个dp更新,dp[i]=dp[i-1]+1,相对简单,不多赘述
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
vector<int>dp(nums.size(),1);
int ret = 1;
for(int i=1;i<nums.size();i++)
{
if(nums[i]>nums[i-1])
dp[i]=dp[i-1]+1;
ret=max(ret,dp[i]);
}
return ret;
}
};
3. 最长重复子数组
1.dp数组的含义:dp[i][j]为第比较i-1和j-1的位置的最大重复子串
2.由于根据上面的含义,我们知道条件比较的是nums1[i-1]和nums2[j-1]的数字是否相同,如果相同则说明此时的长度为上一次比较i-1和j-1之前的两个数字的最长重复子数组的情况加一,也就是dp[i][j]=dp[i-1][j-1]+1;
3.那么之所以定义dp数组为i-1和j-1处的情况,那么初始化中即dp[i][0]和dp[0][j]对应的数应该为0,因为他们并没有实际对应的数进行匹配。如果我们定义为i位置和j位置,那么我们需要遍历两个数组各取一个数字的所有情况进行初始化,这种定义成本过大。
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
int ret = 0;
vector<vector<int>>dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
for(int i=1;i<=nums1.size();i++)
{
for(int j=1;j<=nums2.size();j++)
{
if(nums1[i-1]==nums2[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
}
ret=max(ret,dp[i][j]);
}
}
return ret;
}
};