个人学习 代码随想录 的做题笔记,如果对你有帮助,请一键三连(点赞+收藏+关注)哦~ 感谢支持!欢迎各位在评论区与博主友好讨论!缓慢更新中……
一般从以下几点分别考虑:
1.斐波那契数列:
0,1,1,2,3……
求前两个数之和可得此数列。
public class Solution {
public int Fibonacci(int n) {
// 初始值
if(n <= 0)
return 0;
int[] a = new int[n + 1];
a[0] = 0;
a[1] = 1;
for(int i = 2; i <= n; ++i)
{
// F(n)=F(n-1)+F(n-2)
a[i] = a[i - 1] + a[i - 2];
}
return a[n];
}
}
实际上实现这个数列用a,b,result三个值就可以了。
比如:
int a = 1;
int b = 1;
int result = 0;
for (int i = 3; i <= n; i++)//0,1,1
{
// F(n)=F(n-1)+F(n-2)
result = a + b;
// 更新值
a = b;
b = result;
}
2.最小路径和:
依题意,到F(i,j)能从上面或左面,需要前面的值才能得出接下来的值,建立二维数组存储对应格子的最短路径和
class Solution {
public:
int minPathSum(vector >& grid) {
// write code here
if(grid.size()==0)
return 0;
int m=grid.size();
int n=grid[0].size();
for(int i=1;i
有障碍版本:
自己写的繁琐:
class Solution {
public:
int uniquePathsWithObstacles(vector>& obstacleGrid) {
if(obstacleGrid[obstacleGrid.size()-1][obstacleGrid[0].size()-1]==1)
return 0;
vector>a(obstacleGrid.size(),vector(obstacleGrid[0].size(),0));
for(int i=0;i
大佬写的:
class Solution {
public:
int uniquePathsWithObstacles(vector>& obstacleGrid) {
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
vector> dp(m, vector(n, 0));
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++)
dp[i][0] = 1;
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++)
dp[0][j] = 1;
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (obstacleGrid[i][j] == 1)
continue;
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
};
for循环跳出,进行下一次循环的条件:
if (obstacleGrid[i][j] == 1) continue;
即如果当前格子有障碍,就不算谁能到达它了,此障碍需要被略过。
3.爬楼梯问题
使用最小花费爬楼梯,带权值楼梯。
依旧是从前一个台阶或前两个台阶可以到达当前台阶。
min(F[c.size() - 1], F[c.size() - 2]);
4. 整数拆分
class Solution {
public:
int integerBreak(int n) {
vectora(n+1);
a[2]=1;
for(int i=3;i<=n;i++)
{
for(int j=1;j
5.不同的二叉搜索树
1个节点:1个二叉树
2个节点:2个二叉树
3个节点:5 个二叉树
可以发现:
1为头结点:2个右子树的布局与2个节点时相同;
2为头结点:左子树和右子树与1个节点时相同;
3为头结点:2个左子树的布局与2个节点时相同
此时便可想到一种递推关系:
F(1)=1,
F(2)=F(1)*F(0)+F(1)*F(0)=2, 即左子树为1节点*右子树0个节点的树的个数+左子树为0节点*右子树1个节点的树的个数
F(3)=F[2] * F[0] + F[1] * F[1] + F[0] * F[2]=2*1+1*1+1*2=5
模拟一下该过程可得递推公式,i,j的范围
F[i] += F[以j为头结点左子树节点数] * F[以j为头结点右子树节点数]
j相当于是头结点的元素,从1遍历到i为止。j<=i
递推公式:F(i)+=F(i-j)*F(j-1),j-1 :j为头结点左子树节点数量,i-j :以j为头结点右子树节点数量
class Solution {
public:
int numTrees(int n) {
vectora(n+1);
a[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
a[i]+=a[j-1]*a[i-j];
}
}
return a[n];
}
};
背包:最大容量;物品:价值、体积、每个物品的数量
01背包:每个物品的数量只有一个,放一个或者不放入背包
完全背包:每个物品的数量有无数个,放几个或者不放入背包
二维数组的01背包:
n 个物品和一个大小为 m 的背包. 给定数组 W表示每个物品的大小和数组 V 表示每个物品的
价值
1.子状态:f[i][j]:前i个物品放入大小为j的背包中所获得的最大价值
2.递推状态:装不下和装得下时放还是不放:
①背包容量不够不能放入第i号物品,f[i-1][j];
②背包容量够可以放入第i号物品,包括两种状态:放进去或者不放进去,选择一种最终价值最大的。
f[i-1][j]:不放;
f[i - 1][j - W[i]] + V[i]:先为第i号物品腾出空间,即减去该物品的容量后包内还剩多少容量,再加上i号物品的价值。
f[i][j]=max(f[i-1][j], f[i - 1][j - W[i]] + V[i])。
3.初始值:f[i][0]=0:0~i个物品,背包容量为0时,一个物品都放不进去,价值仍为0。
f[0][j]=0:没放物品价值也为0
4.遍历顺序:双重循环,先遍历谁都行。从递推公式可得f[i][j]由(i-1,j),和(i-1,j - W[i])得到的。在左上角方向(包括正上方向)。
5.返回结果:f(N-1,m);
//动态规划部分代码实现
for (int i = 1; i < N; ++i)
{
for (int j = 1; j != M; ++j)
{
if (A[i - 1] > j)
{
result[i][j] = result[i - 1][j];
}
else
{
int newValue = result[i - 1][j - A[i - 1]] + V[i - 1];
result[i][j] = max(newValue, result[i - 1][j]);
}
}
}
一维数组01背包:
上面的算法在计算第i行元素时,只用到第i-1行的元素,所以二维的空间可以优化为一维空间。
但是如果是一维向量,需要从后向前计算,因为后面的元素更新需要依靠前面的元素未更新的值。
1.子状态:f[j]:大小为j的背包中所获得的最大价值
2.递推状态:装不下和装得下时放还是不放:
①背包容量不够不能放入第i号物品,f[[j];
②背包容量够可以放入第i号物品,包括两种状态:放进去或者不放进去,选择一种最终价值最大的。
f[j]:不放,容量不变;
f[ j - W[i] ] + V[i]:先为第i号物品腾出空间,即减去该物品的容量后包内还剩多少容量,再加上i号物品的价值。
f[ j ]=max( f[ j ], f[ j - W[ i ] ] + V[ i ])。
3.初始值:都初始为0
f[0][j]=0:没放物品价值也为0
4.遍历顺序:双重循环,只能先遍历物品再遍历背包容量,因为如果先遍历背包,就是在当前容量中,现有物品中选出价值最大一个的放入。倒叙遍历是保证物品i只被放入一次。如果正序遍历物品 i 就会被重复加入多次。
二维数组正序遍历是因为f[ i ][ j ]通过上一层f[i - 1][j]计算而来,本层的f[ i ][ j ]不会被覆盖。
5.返回结果:f(m);
class Solution {
public:
int backPackII(int m, vector A, vector V) {
if (A.empty() || V.empty() || m < 1)
{
return 0;
}
const int N = A.size();
const int M = m + 1;
vector result;
result.resize(M, 0);
for (int i = 0; i != N; ++i)
{
for (int j = M - 1; j > 0; --j)
{
if (A[i] > j)
{
result[j] = result[j];
}
else
{
int newValue = result[j - A[i]] + V[i];
result[j] = max(newValue, result[j]);
}
}
}
return result[m];
}
};
6.分割等和子集
每一个数字只能选一次,有最大和的限制-->01背包存在。分割成两个子集,和相等。那直接找和为sum/2的子集就行了。
a[j] (新)= a[j] (旧) || a[j - nums[i]] (旧)
7.最后一块石头的重量
背包最值:每次选2个石头,最终让差值最小。那么把石头分为两堆,每堆石头都选接近(所有石头总重量/2)。把所有石头放进容量为sum/2的背包,求放进去的石头的最大重量x。差值就是sum-2x。
8.目标和
背包组合:要求装满背包容量有几种方法。数组和sum,要被加的数字之和a,要被减的数字(正数)之和b,题目求的目标和 t。
a+b=sum,a-b=t。两式联立求得a=(sum+t)/2,由此可知sum+t为偶数,a也为偶数。
9.一和零
dp[i][j],i个0和j个1时对应的最多子集。
递推:可以从放入当前子集,或者不放入,选出最大的。dp[i][j]不放入,dp[i-zero][j-one]+1在 放入后还应该有的0,1的个数对应的子集数的基础上+1。
三维数组表示:
dp[k][i][j] 表示在前 i个字符串中,使用 i 个 0 和 j 个 1 的情况下最多可以得到的字符串数量。
那去掉一个维度,最外层遍历的就是strs中k个字符串。每个字符串统计0,1个数,遍历物品。内层遍历背包容量i,j,倒序遍历。
10.零钱兑换II
看了题解中有说本题像 爬楼梯 这个题。爬楼梯扩充成每次可走1,2……m阶台阶,到达n级台阶有几种方法,是个排列问题。
每种钱可以用无数次,最终和为已给容量大小。题目给出的样例是组合而不是排列,意为 2+2+1与1+2+2是相同的。外部物品,内部背包,且物品的顺序是不变的。
物品无限次选取,而01背包只能选一次物品,保证下标j之前的dp[j]都是经过计算的就行。
11.组合总和
物品无限,有确定的总和即容量,求排列数,完全背包。按排列计算,不同的排列方式就是不同的方法。
12.零钱兑换
每种硬币的数量是无限的,凑成总金额所需的 最少的硬币个数 :典型完全背包问题。样例显示不强调组合还是排列,硬币有顺序和没有顺序都不影响硬币的最小个数,for循环内外可交换。
13.完全平方数
乍一看,看不出来什么。模拟一遍过程,就看出来是零钱兑换的相似问题了。
注意以下几点:
1.dp[0]=0。为了能进循环并赋值后面的数,实际无意义。除此之外都初始化为INT_MAX,因为求的是最小值
2.物品遍历,本来想的是再建一个物品数组,看了答案才知道可以直接dp[j-i*i].
3.先遍历背包:i从1开始,因为完全平方数从1开始
for(int j=0;j<=n;j++)
{
for(int i=1;i*i<=j;i++)
{
a[j]=min(a[j],a[j-i*i]+1);
}
}
先遍历物品:
for (int i = 1; i * i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (j - i * i >= 0) //判断是否越界
{
dp[j] = min(dp[j - i * i] + 1, dp[j]);
}
}
}
14.单词拆分
字符串是背包,字典是物品。字典里的单词可以无限取出放入背包内。
背包做了这些题,稍微一变是不是感觉自己就不会了呢?
15.打家劫舍
通过前面的计算后面的值,该类题也是典型动态规划问题。
dp[i]:此时两种可能,第i间房被偷了,第i-1号房子就不能偷,dp[i]=dp[i-2]+nums[i];
没偷第i间,第i-1间就能偷,但可以不偷,反正等于前 i-1个房子的最高金额,dp[i]=dp[i-1];
如果在前i-1间时,第i-1间一定被偷了吗?
假设第i-1间没被偷,dp[i-1]=dp[i-2],则第i间可以被偷:
dp[i]=dp[i-1]+num[i]=dp[i-2]+nums[i]。
第i-1间被偷了,那第i间就不能被偷。
所以,dp[i]=max(dp[i]=dp[i-2]+nums[i],dp[i-1])
3.初始值:dp[0]=nums[0],dp[1]=max(nums[0],nums[1])
4.遍历顺序: 从前到后
5.返回结果:dp[nums.size()-1]
16.打家劫舍II
本题可以转为两个子问题。
从下标0号开始考虑,到倒数第二个,即不选最后一个。
从下标1号到最后一个,即不选第一个。
也可以第一个和最后一个都不选,但这种情况包含在上述两种中了,即他们中不一定非选第一个或是最后一个,具体情况具体分析。
分别求他俩的最大值(和上面的题思路一样,不过是取值范围不同),再算他俩谁大。
17.打家劫舍III
只想到了后序遍历……
当前节点被偷,它的子节点不能偷,子节点的子节点可以考虑。不偷当前节点,可以偷它的子节点。子节点的子节点的子节点……递归!
不由得想到三种遍历方法,要分别在左子树,右子树找大的。因为通过递归函数的返回值来做下一步计算。
定义一个数组a,下标0表示不偷,下标1为偷。
如果当前为空就返回{0,0}。
递归遍历左子树,右子树。
单层遍历:当前节点被偷,它的子节点不能偷。不偷当前节点,可以选最大子节点(子节点的子节点)偷。
最终返回{单层遍历的两个值}
18.买卖股票的最佳时机
暴力解非常明显的超时了……
dp[i][0]:
前一天就有股票dp[i-1][0],第i天新买的股票 -prices[i]
dp[i][1]:
前一天没有股票dp[i-1][1],第i天把股票卖出去了dp[i-1][0]+prices[i]
dp[i][0] = max(dp[i - 1][0], -prices[i]);
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
3.初始值:dp[0][0]-=prices[0]:第一天先买股票,dp[0][1]=0:没有股票
4.遍历顺序: 从前到后
5.返回结果:dp[prices.size()-1]
19.买卖股票的最佳时机II
可以多次买卖,那么第i天新买的股票就可能是从前几天没有股票时获得的利润里扣钱,即使最终结果是负值也是正常的。
dp[i][0]:
前一天就有股票dp[i-1][0],第i天新买的股票 dp[i-1][1]-prices[i],唯一与上题不同的地方。
dp[i][0] = max(dp[i - 1][0], dp[i-1][1]-prices[i]);
20.买卖股票的最佳时机III
仅限两笔买卖,一共五种状态。
0:无操作
1:第一次买入
2:第一次卖出
3:第二次买入
4:第二次卖出
1.子状态:dp[i][0],第i天拥有股票时现金的最大金额,dp[i][1],第i天没有股票时现金的最大金额
2. 递推状态:
dp[i][0]:
前一天对股票就没操作dp[i-1][0]
dp[i][1]:
前一天没有股票dp[i-1][1],第i天把股票买了dp[i-1][0]-prices[i]
dp[i][2]:
前一天有股票dp[i-1][2],第i天把股票卖出去了dp[i-1][1]+prices[i]
……
dp[i][0] = dp[i - 1][0];
dp[i][1] = max(dp[i - 1][1], prices[i] - dp[i - 1][0]); dp[i][2]=max(dp[i-1][2],dp[i-1][1]+prices[i]);
……
3.初始值:dp[0][0]=0:dp[0][1]-=prices[0],dp[0][3]-=prices[0]
买入,现金就减少。卖出,现金增加,并且最终取的是最大值,收获利润小于0也没必要计算。
4.遍历顺序: 从前到后
5.返回结果:dp[prices.size()-1][4],因为最后一次卖出的价格一定比第一次卖的贵
21.买卖股票的最佳时机III
通过上题找规律。
0:无操作
1:第一次买入
2:第一次卖出
3:第二次买入
4:第二次卖出
5:第三次买入
6:第三次卖出
……
偶数卖出,奇数买入。
1.子状态:dp[i][j],第i天状态j时现金的最大金额
2. 递推状态:
dp[i][0]:
前一天对股票就没操作dp[i-1][0]
dp[i][1]:
前一天没有股票dp[i-1][1],第i天把股票买了dp[i-1][0]-prices[i]
dp[i][2]:
前一天有股票dp[i-1][2],第i天把股票卖出去了dp[i-1][1]+prices[i]
……
dp[i][0] = dp[i - 1][0];
dp[i][1] = max(dp[i - 1][1], prices[i] - dp[i - 1][0]); dp[i][2]=max(dp[i-1][2],dp[i-1][1]+prices[i]);
……
for(int i=1;i
3.初始值:dp[0][0]=0:dp[0][1]-=prices[0],dp[0][3]-=prices[0]……奇数买入,
买入,现金就减少。卖出,现金增加,并且最终取的是最大值,收获利润小于0也没必要计算。
4.遍历顺序: 从前到后
5.返回结果:dp[prices.size()-1][2*k],因为最后一次卖出的价格一定比第一次卖的贵
22.买股票冷冻期
看了好多题解,分了4个状态:今天买,不持有股票有两种:(前天卖今天保持,今天卖 ),今天冷冻期。
感觉还是两种状态简洁一点,也好理解。但上面这种分多种状态讨论的思想要学会。
0:有股票
前一天就有了,前两条天没有今天买了(买入时要考虑冷冻期:昨天是冷冻期,即前天卖了也就是没有股票,今天才能买)。
那么会有i-2,此时要考虑到i<=1时的赋值。
当只有2天,如果可以买卖。只能第一天买,第二天卖,所以一定是前一天已经买好了。
第一天买为负值,第二天不能买了,就得让0状态保持第一天的状态, 比负值还小。
1:没股票
没股票就是卖出了或者是在冷冻期,不需要管前两天的。
前一天没有,即冷冻期或者处于没有状态。
前一天有,今天卖了。
初始值第一天买入即为负值
class Solution {
public:
int maxProfit(vector& prices) {
int n = prices.size();
if (n == 1) return 0;
vector>dp(n,vector(2,0));
dp[0][0]-=prices[0];
for(int i=1;i1?dp[i-2][1]:0)-prices[i]);
dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[n-1][1];
}
};
23.买股票手续费
比买股票II多了在卖出时减去手续费,因为买卖结束后才算一笔交易,所以不能在买入时就减手续费。
24.最长递增子序列
经典题目!一定要会!
1.子状态:dp[i],前i个字符里最长递增子序列的长度
2.递推状态:如果nums[i]>nums[i-1],dp[i]可由dp[i-1]+1得出,即前i-1个字符再加上第i个字符。
i循环遍历nums数组,令0<= j <=i-1。每次还要比较当前dp[i]和dp[i-1]+1哪一个更大,也就是更新最长子串。
3.初始值:每个字符都设为1个长度。
4.遍历顺序:由dp[i-1]得到所以从前往后。
5.结果:返回的是最长。滚动数组过程中无法确定i遍历完后就是最大,所以要判断遍历完j后当前数组的dp[i]是否最大。
class Solution {
public:
int lengthOfLIS(vector& nums) {
if(nums.size()==1)
return 1;
vectora(nums.size(),1);
int c=0;
//0~i-1,i
for(int i=1;inums[j])
a[i]=max(a[i],a[j]+1);
}
c=max(c,a[i]);
//由于dp[i]表示前i个字符的最大升序长度。i在外层循环,每一行固定i与每一列的j比大小
//要的是最后结果i,但其他行即i取其他值时有可能升序序列长度更大
}
return c;
}
};
25.最长连续递增子序列
关键在于递增,比较dp[i],dp[i-1]的值。递推公式不用再取max了,因为dp[i]意为:以i结尾的递增子序列长度=以i-1结尾的递增序列长度+1。
最终结果要取max,因为自己遍历一遍数组发现最大值不在数组末尾。
递推公式实现后一定要自己算一遍答案。
26.最长公共子序列
1.子状态:两个数组长度不一,采用二维数组分别代表两数组长度。dp[i][j]:n1数组前i个字符与n2数组前j个字符拥有的公共子序列长度。
2.递推状态:题意可知,答案是不连续。从下标1开始。
n1[i-1]与n2[j-1]相等时:dp[i][j]=dp[i-1][j-1]+1。即前从开始到前一个字符得到的最大长度+1
不相等:dp[i][j]=max(dp[i-1][j],dp[i][j-1]) 从n1[0,i-2]与n2[0,j-1],n1[0,i-1]与n2[0,j-2]的最长子序列选最大的
3.初始值:0
4.遍历顺序:正序
5.结果dp[n1.size()][n2.size()]
27.判断子序列
编辑距离系列开始啦!
1.子状态:两数组长度不一,采用二维数组分别代表两数组长度。不连续,dp[i][j]:n1数组前i个字符与n2数组前j个字符拥有的公共子序列长度。
2.递推状态:
相等:a[i][j]=a[i-1][j-1]+1;
不相等:要删除当前字符(j-1),即a[i][j] 要看s[i - 1]与 t[j - 2]的比较结果了:a[i][j] = a[i][j - 1];
3.初始值:a[i][0]表示以i-1为下标结尾的字符串,它与空串的相同序列长度为0
4.遍历顺序:可知a[i][j]来自a[i-1][j-1],a[i][j - 1],所以为正序
5.结果:a[s.size()][t.size()]表示s与t的最长公共序列,如果和s长度相同就得到结果了
其实本题用双指针更简单一点吧~
参考自大佬的题解:
bool isSubsequence(char * s, char * t){
while(*s && *t){
if(*s == *t){//
s++;
}
t++;
}
if(*s == '\0')
return true;
else
return false;
}
28.不同的子序列
如果单独拿出来这题,这类题做得少的话,估计不容易想到动态规划的做法(好吧,博主在说博主自己呜呜)。
1.子状态:两个字符串长度不一,二维数组。dp[i][j]为s以下标为i-1 出现和 t以下标j-1结尾的序列相同的个数。
2.递推状态:要得到dp[i][j],注意dp数组从1开始
相等:s[i-1]==t[j-1]:可能使用s[i-1]--->s[0,i-1]匹配t[0,j-1]--->dp[i-1][j-1],也可能不用--->s[0,i]匹配t[j-1],就是去用s数组里 i 前面的字符--->dp[i-1][j]
寻找bat:
babtbat
不相等:dp[i][j]=dp[i-1][j],既然不相等,就不用s[i-1]来匹配了。
3.初始值:dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; dp[i][j] = dp[i - 1][j];
那么第0行第0列都得初始化:
dp[0][j]:s为空,无法匹配t,=0;
dp[i][0]:t为空,空集是任何字符串的子集,或者理解为,s数组删n个字符,就能得到空串与空串t匹配,=1;
dp[0][0]也和dp[i][0]的理解一致,=1。
4.遍历顺序:来自左上,上面,所以正序。
5.结果:dp[s.size()][t.size()]
放个代码:
发现如果用int类型会溢出,那就用long long吧。
class Solution {
public:
int numDistinct(string s, string t) {
// dp[0][j]=0;
vector> dp(s.size() + 1, vector(t.size() + 1,0));
for(int i=0;i
29.两个字符串的删除操作
本题可以理解为:两个字符串的公共子序列,最后返回两个数组长度相加-公共子序列*2。就能得到最短操作步数。
两个数组都能删了。
class Solution {
public:
int minDistance(string word1, string word2) {
//最长公共子序列
vector>dp(word1.size()+1,vector(word2.size()+1,0));
for(int i=1;i<=word1.size();i++)
{
for(int j=1;j<=word2.size();j++)
{
if(word1[i-1]==word2[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
return word1.size()+word2.size()-dp[word1.size()][word2.size()]*2;
}
};
30.编辑距离
1.子状态:dp[i][j] w1数组下标i-1结尾,与 以j-1下标结尾的w2,的最近编辑距离。
2.递推状态:
w1[i-1]==w2[j-1]:不操作,就等于dp[i-1][j-1]
w1[i-1]!=w2[j-1]:删除、增加、替换
删除:w1删除1个:dp[i-1][j]+1
删除和增加是一样的:w1: ja 和 w2: a,ja删除j或者a增加j,编辑距离一样 dp[i][j-1]+1
替换:w1替换掉w1[i-1],让他与w2[j-1]相等,即w1数组下标i-2结尾,与 以j-2下标结尾的w2,的最近编辑距离。dp[i-1][j-1]+1,在进行替换的数组里:dp[i-1][j-1]表示前面的已经相等了,再加当前i-1下标的字符被替换。
3.初始值:
dp[i][0]:以下标i-1的w1,与空字符串的 最短编辑距离:删除i个
dp[0][j]:以下标j-1的w2,与空字符串的 最短编辑距离:删除j个
4.遍历顺序:
来自左边,上边,左上,所以正序
5.结果:遍历一遍即可得到最后的答案在尾部。
31.回文子串
动态规划和双指针都行,先来看看双指针:
分别以s的每个字符为中心点,向两边扩散,找两边相等的,这是奇数个字符时的做法。
偶数个字符,比如caac,则要以两个中心字符向两边扩散。
动态规划:
1.子状态:dp[i][j]:[i,j]闭区间内是否为回文子串
2.递推状态:
s[i]==s[j]:
①i==j:a是回文子串
②i+1=j:aa也是
③j-1>i:已经s[i]==s[j],下标i与j差了很多,隔了很远,就得缩短空间-->dp[i+1][j-1]判断是否文回文子串。
s[i]!=s[j]:
dp[i][j]=false
3.初始值:
都为false
4.遍历顺序:
dp[i][j]由dp[i+1][j-1],也就是左下方推出,就得从下往上遍历。
5.结果:
数一数总共几个true
class Solution {
public:
int countSubstrings(string s) {
vector>a(s.size(),vector(s.size()+1,false));
int c=0;
for(int i=s.size()-1;i>=0;i--)
{
for(int j=i;j
32.最长回文子序列
1.子状态:dp[i][j],区间内回文子序列的长度
2.递推状态:
s[i]==s[j]:
dp[i][j]=dp[i+1][j-1]+2:加上相等的两字符
s[i]!=s[j]:
更新最大值,dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); 既然两边不相等,那就去掉一边看看是否能为最大值。
s[i] s[i+1] s[j-1] s[j]
3.初始值:
式子无法推出dp[i][i],那就初始为1,其他都为0.
4.遍历顺序:
来自左下,左,下。从下往上遍历。
5.结果:
下->上遍历,左->右,结果应该在右上角。dp[0][s.size()-1]
如果对你有帮助,请一键三连(点赞+收藏+关注)哦~ 感谢支持!让更多的人看到~ 欢迎各位在评论区与博主友好讨论!(◕ᴗ◕✿)
(表情包来源网络)