某一问题有很多重叠子问题
每一状态一定由上一状态推导出来
而贪心没有状态推导,而是直接选局部最优
解决方式:
模拟:举例推导dp数组
检查:打印dp数组
class Solution {
public:
int fib(int n) {
//第i个数的值
vectornum(n+2,0);
//初始化
num[0]=0;
num[1]=1;
//确定遍历顺序
for(int i=2;i<=n;i++){
num[i]=num[i-1]+num[i-2];
}
return num[n];
}
};
递归写法
class Solution {
public:
int fib(int n) {
if(n<2)return n;
return fib(n-1)+fib(n-2);
}
};
class Solution {
public:
int climbStairs(int n) {
vectordp(n+2,0);
//不用考虑dp[0]的初始化,n>0
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
优化空间复杂度O(n)->O(1)
class Solution {
public:
int climbStairs(int n) {
if(n<=2)return n;
int dp[3];
dp[1]=1;
dp[2]=2;
int sum;
for(int i=3;i<=n;i++){
sum=dp[1]+dp[2];
dp[1]=dp[2];
dp[2]=sum;
}
return sum;
}
};
升级:一步一个台阶,两个台阶,...直到m个台阶,有多少种方法爬到n阶
class Solution {
public:
int climbStairs(int n,int m) {
vectordp(n+1,0);
dp[0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m&&j<=i;j++){
dp[i]+=dp[i-j];
}
}
return dp[n];
}
};
class Solution {
public:
int minCostClimbingStairs(vector& cost) {
//从第i个楼梯向上爬的最小花费
int len=cost.size();
vectordp(len,0);
dp[0]=cost[0];
dp[1]=cost[1];
for(int i=2;i
class Solution {
public:
int minCostClimbingStairs(vector& cost) {
int len=cost.size();
int dp0=cost[0];
int dp1=cost[1];
int t;
for(int i=2;i
dfs
class Solution {
public:
int uniquePaths(int m, int n) {
vector>dp(m+1,vector(n+1,0));
for(int i=1;i<=m;i++)dp[i][1]=1;
for(int i=1;i<=n;i++)dp[1][i]=1;
for(int i=2;i<=m;i++){
for(int j=2;j<=n;j++){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m][n];
}
};
滚动数组优化
数论
class Solution {
public:
int uniquePathsWithObstacles(vector>& obstacleGrid) {
int m=obstacleGrid.size(),n=obstacleGrid[0].size();
vector>dp(m,vector(n,0));
for(int i=0;i
class Solution {
public:
int integerBreak(int n) {
//正整数i的最大乘积
vectordp(n+11);
dp[2]=1;//1
dp[3]=2;//2
dp[4]=4;//2*2
dp[5]=6;//2*3
dp[6]=9;//3*3
dp[7]=12;//2*2*3
dp[8]=18;//2*3*3
dp[9]=27;//3*3*3
for(int i=10;i<=n;i++){
for(int k=3;k
2. 从1遍历j,有两种渠道得到dp[i]
一个是j * (i - j) 直接相乘。
一个是j * dp[i - j],相当于是拆分(i - j)
class Solution {
public:
int integerBreak(int n) {
vectordp(n+3);
dp[2]=1;
for(int i=3;i<=n;i++){
for(int j=1;j<=4&&j
3. 贪心解法
每次拆成n个3,如果剩下是4,则保留4,然后相乘,但是这个结论需要数学证明其合理性
class Solution {
public:
int integerBreak(int n) {
if(n==2)return 1;
if(n==3)return 2;
if(n==4)return 4;
int ans=1;
while(n>=3){
ans*=3;
n-=3;
}
if(n==1)ans=ans*4/3;
if(n==2)ans*=2;
return ans;
}
};
class Solution {
public:
int numTrees(int n) {
vectordp(n+1,0);
dp[0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
//对于第i个节点,需要考虑1作为根节点直到i作为根节点的情况,所以需要累加
//一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j
dp[i]+=dp[j-1]*dp[i-j];
}
}
return dp[n];
}
};
dp[3],就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量 + 元素3为头结点搜索树的数量
元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量
元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量
元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量
01背包
暴力解法:回溯,每个物品取或不取,时间复杂度O(2^n)
暴力解法指的是时间复杂度是指数级别的
滚动数组优化->二维变一维
第二维为什么要反向遍历?
防止物品重复放入
初步可以想用回溯法,但是N可以到100,会超时
原本设置dp[][]为int类型,但值会超int大小,设置为bool
class Solution {
public:
bool canPartition(vector& nums) {
int sum=0;
int len=nums.size();
for(int i=0;i>dp(len,vector(sum/2+1,0));
if(nums[0]<=sum/2)dp[0][nums[0]]=true;
else return false;
//在前i个数中选
for(int i=1;i=nums[i]&&dp[i-1][j-nums[i]])dp[i][j]=true;
//printf("i==%d j==%d dp=%d\n",i,j,dp[i][j]);
}
}
return dp[len-1][sum/2];
return false;
}
};
优化为一维
class Solution {
public:
bool canPartition(vector& nums) {
int sum=0;
int len=nums.size();
for(int i=0;idp(sum/2+1,0);
if(nums[0]<=sum/2)dp[nums[0]]=true;
else return false;
//在前i个数中选
for(int i=1;i=0;j--){
if(j>=nums[i]&&dp[j-nums[i]])dp[j]=true;
//printf("i==%d j==%d dp=%d\n",i,j,dp[i][j]);
}
}
return dp[sum/2];
return false;
}
};
尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小
class Solution {
public:
int lastStoneWeightII(vector& stones) {
int len=stones.size();
int sum=0;
for(int i=0;idp(target+1,0);
for(int i=0;i=stones[i];j--){
dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
}
}
return sum-dp[target]-dp[target];
}
};
本题要如何使表达式结果为target,
既然为target,那么就一定有 left组合 - right组合 = target。
left + right等于sum,而sum是固定的。
公式来了, left - (sum - left) = target -> left = (target + sum)/2 。
target是固定的,sum是固定的,left就可以求出来。
此时问题就是在集合nums中找出和为left的组合。
class Solution {
public:
int findTargetSumWays(vector& nums, int target) {
int len=nums.size();
int sum=0;
for(int i=0;isum)return false;
if((target+sum)%2==1)return false;
int s=(target+sum)/2;
vectordp(s+1,0);
dp[0]=1;
for(int i=0;i=nums[i];j--){
dp[j]+=dp[j-nums[i]];
}
}
return dp[s];
}
};
是典型的01背包,但是物品重量有两个维度
typedef pairPII;
#define x first
#define y second
class Solution {
public:
int findMaxForm(vector& strs, int m, int n) {
//前0后1
vector>dp(m+1,vector(n+1,0));
for(int i=0;i=x;j--){
for(int k=n;k>=y;k--){
dp[j][k]=max(dp[j][k],dp[j-x][k-y]+1);
//printf("j==%d k==%d dp=%d\n",j,k,dp[j][k]);
}
}
}
return dp[m][n];
}
};
完全背包:
class Solution {
public:
int change(int amount, vector& coins) {
int len=coins.size();
//前i个面额的硬币,可以组成总金额为j的最大组合数
vectordp(amount+1,0);
dp[0]=1;
for(int i=0;i
class Solution {
public:
int combinationSum4(vector& nums, int target) {
int len=nums.size();
vectordp(target+1,0);
dp[0]=1;
//求排列数
for(int j=0;j<=target;j++){
for(int i=0;i=nums[i]&&dp[j]
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
完全背包求排列问题->遍历背包放在外面,遍历物品放在里面
class Solution {
public:
int climbStairs(int n) {
vectordp(n+2,0);
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++){
for(int j=1;j<=2&&j<=i;j++){
dp[i]+=dp[i-j];
}
}
return dp[n];
}
};
class Solution {
public:
int coinChange(vector& coins, int amount) {
int len=coins.size();
//凑成总金额所需的最小硬币个数
vectordp(amount+1,0x3f3f3f3f);
dp[0]=0;
for(int i=0;i
class Solution {
public:
int numSquares(int n) {
vectordp(n+1,0x3f3f3f3f);
dp[0]=0;
for(int i=1;i<=n/i;i++){
//printf("i==%d\n",i);
for(int j=i*i;j<=n;j++){
dp[j]=min(dp[j],dp[j-i*i]+1);
//printf("j==%d dp==%d\n",j,dp[j]);
}
}
return dp[n];
}
};
切割问题类似组合问题
时间复杂度O(2^n)每个单词都有两种状态-切割和不切割
class Solution {
public:
bool dfs(int st,const string&s,const unordered_set&wordSet){
if(st>=s.size())return true;
for(int i=st;i& wordDict) {
unordered_setwordSet(wordDict.begin(),wordDict.end());
return dfs(0,s,wordSet);
}
};
利用记忆数组存储递归过程中计算的结果--记忆化递归
时间复杂度O(2^n)
class Solution {
public:
bool dfs(int st,const string&s,const unordered_set&wordSet,vector&memory){
if(st>=s.size()){
return true;
}
if(memory[st]!=-1)return memory[st];
for(int i=st;i& wordDict) {
unordered_setwordSet(wordDict.begin(),wordDict.end());
vectormemory(s.size(),-1);
return dfs(0,s,wordSet,memory);
}
};
组合(先遍历物品再遍历背包)---->这道题用组合只能过39/45
class Solution {
public:
bool wordBreak(string s, vector& wordDict) {
unordered_setwordSet(wordDict.begin(),wordDict.end());
//是否可以用字典中的单词拼出s0~i
//背包问题
//完全背包
//组合、排列都ok????
//如果将word作为物品其实只能用排列,用过的word不能再用
//无法通过的样例
//"applepenapple"
//["apple","pen"]
//组合写法 常常是-x-x-x-y-y-y-y-z-z
//排列可能涉及,x-y-z-z-x-y,所以这题不能用
vectordp(s.size()+1,0);
dp[0]=true;
for(int i=0;i
正解:
class Solution {
public:
bool wordBreak(string s, vector& wordDict) {
unordered_setwordSet(wordDict.begin(),wordDict.end());
vectordp(s.size()+1,0);
//排列
dp[0]=true;
for(int i=1;i<=s.size();i++){
for(int j=0;js.size())continue;
string word=s.substr(i-1,wordDict[j].size());
//cout<
代码随想录解法:
对区间i-j上字符进行判断
class Solution {
public:
bool wordBreak(string s, vector& wordDict) {
unordered_set wordSet(wordDict.begin(), wordDict.end());
vector dp(s.size() + 1, false);
dp[0] = true;
for (int i = 1; i <= s.size(); i++) { // 遍历背包
for (int j = 0; j < i; j++) { // 遍历物品
string word = s.substr(j, i - j); //substr(起始位置,截取的个数)
if (wordSet.find(word) != wordSet.end() && dp[j]) {
dp[i] = true;
}
}
}
return dp[s.size()];
}
};
多重背包和01背包最为相似
将多重背包转换为01背包的方法
vector weight = {1, 3, 4};
vector value = {15, 20, 30};
vector nums = {2, 3, 2};
int bagWeight = 10;
for (int i = 0; i < nums.size(); i++) {
while (nums[i] > 1) { // nums[i]保留到1,把其他物品都展开
weight.push_back(weight[i]);
value.push_back(value[i]);
nums[i]--;
}
}
class Solution {
public:
int rob(vector& nums) {
//打劫到第i家得到的最高金额
vectordp(1+nums.size(),0);
dp[0]=0;
dp[1]=nums[0];
for(int i=2;i<=nums.size();i++)dp[i]=max(dp[i-2]+nums[i-1],dp[i-1]);
return dp[nums.size()];
}
};
注意打劫可以隔2个房间,跟奇偶无关
注意边界
class Solution {
public:
int robnum(int st,int ed,vector& nums){
//!!!
if(st==ed)return nums[st];
int len=ed-st+1;
vectordp(ed+1,0);
dp[st]=nums[st];
dp[st+1]=max(dp[st],nums[st+1]);
for(int i=st+2;i<=ed;i++)dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
return dp[ed];
}
int rob(vector& nums) {
int len=nums.size();
//!!!!
if(len==0)return 0;
if(len==1)return nums[0];
//有2种情况 1.头+中 2.中+尾
return max(robnum(0,len-2,nums),robnum(1,len-1,nums));
}
};
法一:暴力递归超时
空间复杂度O(logn)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int rob(TreeNode* root) {
if(root==NULL)return 0;
if(root->left==NULL&&root->right==NULL)return root->val;
int res0=0,res1=root->val;
//偷父节点
if(root->left)res1+=rob(root->left->left)+rob(root->left->right);
if(root->right)res1+=rob(root->right->left)+rob(root->right->right);
//不偷
if(root->right)res0+=rob(root->right);
if(root->left)res0+=rob(root->left);
return max(res0,res1);
}
};
法二:记忆化递推
时间复杂度O(n)
空间复杂度O(logn)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
unordered_mapumap;
int rob(TreeNode* root) {
if(root==NULL)return 0;
if(root->left==NULL&&root->right==NULL)return root->val;
if(umap[root])return umap[root];
int res0=0,res1=root->val;
//偷父节点
if(root->left)res1+=rob(root->left->left)+rob(root->left->right);
if(root->right)res1+=rob(root->right->left)+rob(root->right->right);
//不偷
if(root->right)res0+=rob(root->right);
if(root->left)res0+=rob(root->left);
umap[root]=max(res1,res0);
return umap[root];
}
};
法三:动态规划
用长度为2的数组记录每个节点偷与不偷
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector robTree(TreeNode *root){
if(root==NULL)return {0,0};
vector left=robTree(root->left);
vector right=robTree(root->right);
//偷
int val1=root->val+left[0]+right[0];
//不偷
int val0=max(left[0],left[1])+max(right[0],right[1]);
return {val0,val1};
}
int rob(TreeNode* root) {
vectorans=robTree(root);
return max(ans[0],ans[1]);
}
};
只能交易一次
法一:暴力解法 O(n^2)超时
class Solution {
public:
int maxProfit(vector& prices) {
int len=prices.size();
int ans=0;
for(int i=0;i
法二:贪心,取左最小值,右最大值
时间复杂度O(n)
class Solution {
public:
int maxProfit(vector& prices) {
int len=prices.size();
int ans=0;
vectorlow(len,0);
low[0]=prices[0];
for(int i=1;i
法三:动态规划
dp[i][0]表示第i天时持有股票所得最多的现金
dp[i][1]表示第i天时不持有股票所得最多现金
注意:第i天时持有不是第i天买入
class Solution {
public:
int maxProfit(vector& prices) {
int len=prices.size();
vector>dp(len,vector(2,0));
//第i天持有股票所得最多的现金dp[i][0]
//第i天不持有股票所得的最多现金
dp[0][0]=-prices[0];
dp[0][1]=0;
for(int i=1;i
优化空间复杂度
class Solution {
public:
int maxProfit(vector& prices) {
int len=prices.size();
vector>dp(2,vector(2,0));
dp[0][0]=-prices[0];
dp[0][1]=0;
for(int i=1;i
可以无限次交易
class Solution {
public:
int maxProfit(vector& prices) {
int len=prices.size();
vector>dp(len,vector(2,0));
dp[0][0]=-prices[0];
dp[0][1]=0;
for(int i=1;i
最多交易两次
class Solution {
public:
int maxProfit(vector& prices) {
int len=prices.size();
//第i天所对应的状态
//最多可以完成两笔交易
//1.无交易
//2.买入第一次
//3.卖出第一次
//4.买入第二次
//5.卖出第二次
vector>dp(len+2,vector(5,0));
dp[0][0]=0;
dp[0][1]=-prices[0];
//一定要初始化dp[0][3],循环里的逻辑也是可以一天买两次
dp[0][3]=-prices[0];
for(int i=1;i
最多交易k次
class Solution {
public:
int maxProfit(int k, vector& prices) {
int len=prices.size();
if(len==0||len==1)return 0;
vector>dp(len+1,vector(2*k+1,0));
for(int i=0;i<=2*k;i++){
if(i%2)dp[0][i]=-prices[0];
}
for(int i=1;i
class Solution {
public:
int maxProfit(vector& prices) {
int len=prices.size();
vector>dp(len+1,vector(5,0));
//1.买入状态
//卖出状态:
//2.度过冷冻期保持卖出
//3.今天卖出
//4.冷冻期
dp[0][1]=-prices[0];
for(int i=1;i
class Solution {
public:
int maxProfit(vector& prices, int fee) {
int len=prices.size();
vector>dp(len+1,vector(2,0));
dp[0][1]=-prices[0];
for(int i=1;i
class Solution {
public:
int lengthOfLIS(vector& nums) {
int len=nums.size();
//以nums[i]为结尾的递增子序列长度
vectordp(len+1,1);
int ans=1;
for(int i=1;inums[j])dp[i]=max(dp[j]+1,dp[i]);
}
ans=max(ans,dp[i]);
}
return ans;
}
};
class Solution {
public:
int findLengthOfLCIS(vector& nums) {
int len=nums.size();
//以nums[i]结尾的最长连续递增子序列
vectordp(len+1,1);
int ans=1;
for(int i=1;inums[i-1])dp[i]=dp[i-1]+1;
ans=max(ans,dp[i]);
}
return ans;
}
};
注意是连续的
class Solution {
public:
int findLength(vector& nums1, vector& nums2) {
int len1=nums1.size(),len2=nums2.size();
//nums1的前i个和nums2的前j个中有多少个重复的
vector>dp(len1+1,vector(len2+1,0));
int ans=0;
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(nums1[i-1]==nums2[j-1])dp[i][j]=dp[i-1][j-1]+1;
ans=max(ans,dp[i][j]);
}
}
return ans;
}
};
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int len1=text1.size(),len2=text2.size();
//nums1的前i个和nums2的前j个中有多少个重复的
vector>dp(len1+1,vector(len2+1));
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(text1[i-1]==text2[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 dp[len1][len2];
}
};
class Solution {
public:
int maxUncrossedLines(vector& nums1, vector& nums2) {
int len1=nums1.size(),len2=nums2.size();
vector>dp(len1+1,vector(len2+1,0));
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(nums1[i-1]==nums2[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 dp[len1][len2];
}
};
class Solution {
public:
int maxSubArray(vector& nums) {
int len=nums.size();
//j~i连续数组总和
vector>dp(len+1,vector(len+1,0));
int ans=nums[0];
for(int i=1;i<=len;i++){
for(int j=1;j<=i;j++){
dp[j][i]=dp[j][i-1]+nums[i-1];
ans=max(ans,dp[j][i]);
}
}
return ans;
}
};
class Solution {
public:
int maxSubArray(vector& nums) {
int len=nums.size();
//以i结尾的具有最大和的连续数组
vectordp(len+1,0);
int ans=nums[0];
for(int i=1;i<=len;i++){
dp[i]=max(dp[i-1]+nums[i-1],nums[i-1]);
ans=max(ans,dp[i]);
}
return ans;
}
};
法一:最长公共子序列值大于等于子串长度,时间复杂度O(n*m)
class Solution {
public:
bool isSubsequence(string s, string t) {
int len1=s.size(),len2=t.size();
//s到i,t到j的最长公共子序列长度
vector>dp(len1+1,vector(len2+1,0));
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(s[i-1]==t[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 dp[len1][len2]>=len1;
}
};
法二:双指针O(n)
class Solution {
public:
bool isSubsequence(string s, string t) {
int len1=s.size(),len2=t.size();
bool flag=true;
for(int i=0,j=-1;i
如果不是子序列,而是要求连续序列的,那就可以考虑用KMP
本题相当于编辑距离只有删除操作,不用考虑替换增加
class Solution {
public:
int numDistinct(string s, string t) {
int len1=s.size(),len2=t.size();
vector>dp(len1+1,vector(len2+1,0));
//dp[i][j]是s[0~i-1]的子序列中t[0~t-1]的出现个数
//以i-1为结尾的s可以随便删除元素,出现空字符串的个数。
for(int i=0;i<=len1;i++)dp[i][0]=1;
for(int i=1;i<=len2;i++)dp[0][i]=0;
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
//s[i-1]==t[i-1]时不用进行编辑
//dp[i-1][j]相当于删除s[i-1]
//相同时可以用s[i-1]进行匹配,也可以不用s[i-1]进行匹配
//bagg bag
//不相同时要进行编辑
if(s[i-1]==t[j-1])dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
else dp[i][j]=dp[i-1][j];
}
}
return dp[len1][len2];
}
};
class Solution {
public:
int minDistance(string word1, string word2) {
int len1=word1.size(),len2=word2.size();
vector>dp(len1+1,vector(len2+1,0));
for(int i=0;i<=len1;i++)dp[i][0]=i;
for(int i=0;i<=len2;i++)dp[0][i]=i;
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(word1[i-1]==word2[j-1]){
dp[i][j]=dp[i-1][j-1];
}else{
dp[i][j]=min({dp[i-1][j-1]+2,dp[i][j-1]+1,dp[i-1][j]+1});
}
}
}
return dp[len1][len2];
}
};
class Solution {
public:
int minDistance(string word1, string word2) {
int len1=word1.size(),len2=word2.size();
vector>dp(len1+1,vector(len2+1,0));
for(int i=0;i<=len1;i++)dp[i][0]=i;
for(int i=0;i<=len2;i++)dp[0][i]=i;
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(word1[i-1]==word2[j-1])dp[i][j]=dp[i-1][j-1];
else dp[i][j]=min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
}
}
return dp[len1][len2];
}
};
遍历顺序一定从下到上,从左到右
class Solution {
public:
int countSubstrings(string s) {
int len=s.size();
vector>dp(len+1,vector(len+1,false));
int res=0;
for(int i=len;i>=1;i--){
for(int j=i;j<=len;j++){
if(s[i-1]==s[j-1]){
if(i==j)dp[i][j]=true;
else if(j-i==1)dp[i][j]=true;
else if(dp[i+1][j-1])dp[i][j]=true;
if(dp[i][j]){
res++;
}
}else{
dp[i][j]=false;
}
}
}
return res;
}
};
class Solution {
public:
int longestPalindromeSubseq(string s) {
int len=s.size();
vector>dp(len+1,vector(len+1,1));
int ans=1;
for(int i=len;i>=1;i--){
for(int j=i+1;j<=len;j++){
if(s[i-1]==s[j-1]){
if(j-i==1)dp[i][j]=2;
if(j-i>1)dp[i][j]=dp[i+1][j-1]+2;
}else{
dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
}
}
}
return dp[1][len];
}
};