一.斐波那契数列模型
1.第N个泰波那契数
1137. 第 N 个泰波那契数 - 力扣(LeetCode)
class Solution
{
public:
int tribonacci(int n)
{
//防止越界,先处理边界值
if(n==0) return 0;
if(n==1) return 1;
if(n==2) return 1;
//1.创建dp表
vector<int> dp(n+1); //因为dp表下标从0开始,所以要找第n个dp的值就需要dp(n+1)
//2.初始化
dp[0]=0;
dp[1]=1;
dp[2]=1;
//3.填表
for(int i=3;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
}
//4.返回值
return dp[n];
}
};
class Solution
{
public:
int tribonacci(int n)
{
//防止越界,先处理边界值
if(n==0) return 0;
if(n==1) return 1;
if(n==2) return 1;
//1.创建滚动变量
int a=0,b=1,c=1,d=0;
//2.填表
for(int i=3;i<=n;i++)
{
d=a+b+c;
//3.滚动操作
a=b;
b=c;
c=d;
}
//4.返回值
return d;
}
};
2. 三步问题
面试题 08.01. 三步问题 - 力扣(LeetCode)
class Solution
{
public:
int waysToStep(int n)
{
//0.处理边界情况
if(n==1) return 1;
if(n==2) return 2;
if(n==3) return 4;
//1.创建dp表
vector<int> dp(n+1);
//2.初始化
dp[1]=1,dp[2]=2,dp[3]=4;
//3.填表
for(int i=4;i<=n;i++)
{
dp[i]=((dp[i-1]+dp[i-2])%1000000007+dp[i-3])%1000000007;
}
//4.返回值
return dp[n];
}
};
3.使用最小花费爬楼梯
解法一:i为当前位置的终点
class Solution
{
public:
int minCostClimbingStairs(vector<int>& cost)
{
//1.创建dp表
int n=cost.size();
vector<int> dp(n+1);
//2.初始化
dp[0]=0,dp[1]=0;
//3.填表
for(int i=2;i<=n;i++)
{
dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
//4.返回值
return dp[n];
}
};
解法二:i为当前位置的起点
class Solution
{
public:
int minCostClimbingStairs(vector<int>& cost)
{
//1.创建dp表
int n=cost.size();
vector<int> dp(n);
//2.初始化
dp[n-1]=cost[n-1],dp[n-2]=cost[n-2];
//3.填表
for(int i=n-3;i>=0;i--)
{
dp[i]=min(dp[i+1]+cost[i],dp[i+2]+cost[i]);
}
//4.返回值
return min(dp[0],dp[1]);
}
};
4.解码方法(再看一遍)
class Solution
{
public:
int numDecodings(string s)
{
//1.创建dp表
int n=s.size();
vector<int> dp(n);
//2.初始化
if(s[0] == '0') dp[0]=0; //按单字母解码
else dp[0]=1;
if(n==1) return dp[0];
if(s[0]!='0' && s[1]!='0') dp[1]++; //按单字母解码两个字母
int t=(s[0]-'0')*10+(s[1]-'0');
if(t>=10 && t<=26) dp[1]++; //按双字母解码
//3.填表
for(int i=2;i<n;i++)
{
if(s[i]!='0') dp[i] += dp[i-1]; //单字母编码能编码成功,说明前dp[i-1]种编码方式且第i位置单编码是成立的
int t=(s[i-1]-'0')*10+(s[i]-'0');
if(t>=10 && t<=26) dp[i] += dp[i-2]; //双字母编码能编码成功,说明前dp[i-2]种编码方式且第i位置双编码是成立的
}
//4.返回值
return dp[n-1];
}
};
二.路径问题
1.不同路径
class Solution
{
public:
int uniquePaths(int m, int n)
{
//1.创建dp表
vector<vector<int>> dp(m+1,vector<int>(n+1)); //多加一行一列,可以省去很多的越界情况
//2.初始化
dp[0][1]=1;
//3.填表
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
}
//4.返回值
return dp[m][n];
}
};
2.不同路径Ⅱ
class Solution
{
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid)
{
//1.创建dp表
int m=obstacleGrid.size(),n=obstacleGrid[0].size();
vector<vector<int>> dp(m+1,vector<int>(n+1));
//2.初始化
dp[0][1]=1;
//3.填表
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(obstacleGrid[i-1][j-1]==1)
{
dp[i][j]=0;
}
else
{
dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
}
}
//4.返回值
return dp[m][n];
}
};
3.礼物的最大价值
LCR 166. 珠宝的最高价值 - 力扣(LeetCode)
class Solution
{
public:
int jewelleryValue(vector<vector<int>>& frame)
{
//1.建立dp表
int m=frame.size(),n=frame[0].size();
vector<vector<int>> dp(m+1,vector<int>(n+1));
//2.初始化
//3.填表
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+frame[i-1][j-1];
}
}
//4.返回值
return dp[m][n];
}
};
4.下降路径最小和
class Solution
{
public:
int minFallingPathSum(vector<vector<int>>& matrix)
{
int n=matrix.size();
//1.建立dp表
vector<vector<int>> dp(n+1,vector<int>(n+2,INT_MAX));
//2.初始化
for(int j=0;j<n+2;j++) dp[0][j]=0;
//3.填表
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=min(dp[i-1][j-1],min(dp[i-1][j],dp[i-1][j+1]))+matrix[i-1][j-1];
}
}
//4.返回值
int min=INT_MAX;
for(int i=1;i<=n;i++)
{
if(dp[n][i] < min) min=dp[n][i];
}
return min;
}
};
5.最小路径和
class Solution
{
public:
int minPathSum(vector<vector<int>>& grid)
{
//1.创建dp表
int m=grid.size(),n=grid[0].size();
vector<vector<int>> dp(m+1,vector<int>(n+1,INT_MAX));
//2.初始化
dp[0][1]=0;
//3.填表
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1];
}
}
//4.返回值
return dp[m][n];
}
};
6.地下城游戏
class Solution
{
public:
int calculateMinimumHP(vector<vector<int>>& dungeon)
{
//1.创建dp表
int m=dungeon.size(),n=dungeon[0].size();
vector<vector<int>> dp(m+1,vector<int>(n+1,INT_MAX ));
//2.初始化
dp[m][n-1]=1,dp[m-1][n]=1;
//3.填表
for(int i=m-1;i>=0;i--) //倒着填表
{
for(int j=n-1;j>=0;j--)
{
dp[i][j]=min(dp[i+1][j],dp[i][j+1])-dungeon[i][j];
dp[i][j]=max(1,dp[i][j]);
}
}
//4.返回值
return dp[0][0];
}
};
三.简单多状态dp问题(以i位置结尾时,有多个状态,针对每个状态,都要有一个dp方程)
1.按摩师(在i位置时有两个状态)
class Solution
{
public:
int massage(vector<int>& nums)
{
//1.创建dp表
int n=nums.size();
if(n==0) return 0; //处理边界情况
vector<int> f(n); //f表示选到i位置时,nums[i]要选,的预约最大值
vector<int> g(n); //g表示选到i位置时,nums[i]不选,的预约最大值
//2.初始化
f[0]=nums[0];
g[0]=0;
//3.填表
for(int i=1;i<n;i++)
{
f[i]=g[i-1]+nums[i];
g[i]=max(f[i-1],g[i-1]);
}
//4.返回值
return max(f[n-1],g[n-1]);
}
};
2.打家劫舍(在i位置时有两个状态)
class Solution
{
public:
int rob(vector<int>& nums)
{
//1.创建dp表
int n=nums.size();
if(n==0) return 0;
vector<int> f(n); //抢第i个的最大值
vector<int> g(n); //不强第i个的最大值
//2.初始化
f[0]=nums[0];
g[0]=0;
//3.填表
for(int i=1;i<n;i++)
{
f[i]=g[i-1]+nums[i];
g[i]=max(g[i-1],f[i-1]);
}
//4.返回值
return max(f[n-1],g[n-1]);
}
};
3.打家劫舍Ⅱ(在i位置时有两个状态)
class Solution
{
public:
int rob1(vector<int>& nums,int l,int r)
{
int len=r-l+1;
//1.创建dp表
vector<int> f(len);
vector<int> g(len);
//2.初始化
f[0]=nums[l];
g[0]=0;
//3.填表
for(int i=1;i<len;i++)
{
f[i]=nums[l+i]+g[i-1];
g[i]=max(f[i-1],g[i-1]);
}
//4.返回值
return max(f[len-1],g[len-1]);
}
int rob(vector<int>& nums)
{
int n=nums.size();
if(n==1) return nums[0];
if(n==2) return max(nums[0],nums[1]);
if(n==3) return max(nums[2],max(nums[0],nums[1]));
int x=nums[0]+rob1(nums,2,n-2); //抢第一个
int y=rob1(nums,1,n-1); //不抢第一个
return max(x,y);
}
};
4.删除并获得点数(在i位置时有两个状态)
class Solution
{
public:
int deleteAndEarn(vector<int>& nums)
{
const int N=10001;
//1.创建dp表
int arr[N]={0};
vector<int> f(N);
vector<int> g(N);
//2.初始化
for(auto x:nums)
{
arr[x] += x;
}
f[0]=arr[0];
g[0]=0;
//3.填表
for(int i=1;i<N;i++)
{
f[i]=arr[i]+g[i-1];
g[i]=max(f[i-1],g[i-1]);
}
//4.返回值
return max(f[N-1],g[N-1]);
}
};
5.粉刷房子(在i位置时有三个状态)
class Solution
{
public:
int minCost(vector<vector<int>>& costs)
{
//1.创建dp表
int n=costs.size();
int dp[100][3]={0};
//2.初始化
dp[0][0]=costs[0][0];
dp[0][1]=costs[0][1];
dp[0][2]=costs[0][2];
//3.填表
for(int i=1;i<n;i++)
{
dp[i][0]=costs[i][0]+min(dp[i-1][1],dp[i-1][2]);
dp[i][1]=costs[i][1]+min(dp[i-1][0],dp[i-1][2]);
dp[i][2]=costs[i][2]+min(dp[i-1][1],dp[i-1][0]);
}
//4.返回值
return min(dp[n-1][0],min(dp[n-1][1],dp[n-1][2]));
}
};
6.买卖股票最佳时期含冷冻期(在i位置时有三个状态)
309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode)
class Solution
{
public:
int maxProfit(vector<int>& prices)
{
int n=prices.size();
//1.创建dp表
vector<vector<int>> dp(n,vector<int>(3));
//2.初始化
dp[0][0]=-prices[0];
dp[0][1]=0;
dp[0][2]=0;
//3.填表
for(int i=1;i<n;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][2]); //可买
dp[i][2]=dp[i-1][0]+prices[i]; //进入冷冻期
}
//4.返回值
return max(dp[n-1][0],max(dp[n-1][1],dp[n-1][2]));
}
};
7.买卖股票的最佳时机含手续费(在i位置时有两个状态)
714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)
class Solution
{
public:
int maxProfit(vector<int>& prices, int fee)
{
//1.创建dp表
int n=prices.size();
vector<int> f(n);
vector<int> g(n);
//2.初始化
f[0]=-prices[0];
g[0]=0;
//3.填表
for(int i=1;i<n;i++)
{
f[i]=max(f[i-1],g[i-1]-prices[i]); //可卖
g[i]=max(g[i-1],f[i-1]+prices[i]-fee); //可买
}
//4.返回值
return max(f[n-1],g[n-1]);
}
};
8.买卖股票最佳时机
123. 买卖股票的最佳时机 III - 力扣(LeetCode)
class Solution
{
public:
const int MIN = -0x3f3f3f;
int maxProfit(vector<int>& prices)
{
//1.创建dp表
int n=prices.size();
vector<vector<int>> f(n,vector<int> (3,MIN));
vector<vector<int>> g(n,vector<int> (3,MIN));
//2.初始化
f[0][0]=-prices[0];
g[0][0]=0;
//3.填表
for(int i=1;i<n;i++) //天数
{
for(int j=0;j<3;j++) //次数
{
f[i][j]=max(f[i-1][j],g[i-1][j]-prices[i]);
g[i][j]=g[i-1][j]; //该状态一定存在
if(j-1>=0) //该状态可能存在
g[i][j]=max(g[i][j],f[i-1][j-1]+prices[i]);
}
}
//4.返回值
int Max=MIN;
for(int x:g[n-1]) Max=max(Max,x);
return Max;
}
};
9.买卖股票最佳时机
188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
class Solution
{
public:
const int MIN = -0x3f3f3f;
int maxProfit(int k, vector<int>& prices)
{
//1.创建dp表
int n=prices.size();
vector<vector<int>> f(n,vector<int> (k+1,MIN));
vector<vector<int>> g(n,vector<int> (k+1,MIN));
//2.初始化
f[0][0]=-prices[0];
g[0][0]=0;
//3.填表
for(int i=1;i<n;i++)
{
for(int j=0;j<=k;j++)
{
f[i][j]=max(f[i-1][j],g[i-1][j]-prices[i]);
g[i][j]=g[i-1][j];
if(j-1>=0)
g[i][j]=max(g[i][j],f[i-1][j-1]+prices[i]);
}
}
//4.返回值
int Max=MIN;
for(int x:g[n-1]) Max=max(Max,x);
return Max;
}
};
四.子数组系列
1.最大子数组和
class Solution
{
public:
int maxSubArray(vector<int>& nums)
{
//1.创建dp表
int n=nums.size();
if(n==1) return nums[0];
vector<int> dp(n);
//2.初始化
dp[0]=nums[0];
//3.填表
for(int i=1;i<n;i++)
{
dp[i]=max(nums[i],dp[i-1]+nums[i]);
}
//4.返回值
int Max=INT_MIN;
for(int x:dp) Max=max(Max,x);
return Max;
}
};
2.环形子数组的最大和
class Solution
{
public:
int maxSubarraySumCircular(vector<int>& nums)
{
//1.建立dp表
int n=nums.size(),sum=0;
if(n==1) return nums[0];
for(int x:nums) sum += x;
vector<int> f(n); //以i为结尾最大和
vector<int> g(n); //以i为结尾最小和
//2.初始化
f[0]=nums[0];
g[0]=nums[0];
//3.填表
for(int i=1;i<n;i++)
{
f[i]=max(nums[i]+f[i-1],nums[i]);
g[i]=min(nums[i]+g[i-1],nums[i]);
}
//4.返回值
int fmax=INT_MIN;
int gmin=INT_MAX;
for(int x:f) fmax=max(fmax,x);
for(int x:g) gmin=min(gmin,x);
return sum==gmin?fmax:max(fmax,sum-gmin); //当全为负数的时候sum-gmin为0,是特殊情况
}
};
3.乘积最大子数组
class Solution
{
public:
int maxProduct(vector<int>& nums)
{
//1.创建dp表
int n=nums.size();
if(n==1) return nums[0];
vector<int> f(n); //f表用来存最大值
vector<int> g(n); //g表用来存最小值,最小值乘负数就成最大值了
//2.初始化
f[0]=nums[0];
g[0]=nums[0];
//3.填表
int fmax=nums[0],gmin=nums[0];
for(int i=1;i<n;i++)
{
f[i]=max(nums[i],max(f[i-1]*nums[i],g[i-1]*nums[i])); //长度为一,nums[i]大小于0
fmax=max(fmax,f[i]);
g[i]=min(nums[i],min(f[i-1]*nums[i],g[i-1]*nums[i])); //nums[i]大于0乘最小值,小于0乘最大值
}
//4.返回值
return fmax;
}
};
4.乘积为正数的最长子数组长度
1567. 乘积为正数的最长子数组长度 - 力扣(LeetCode)
class Solution
{
public:
int getMaxLen(vector<int>& nums)
{
//1.创建dp表
int n=nums.size();
vector<int> f(n); //f表示以i为结尾最大正数积的最大长度
vector<int> g(n); //g表示以i为结尾最小负数积的最大长度
//当nums等于0时直接不用考虑,那个位置不存在使结果为正数的长度,0就放着就可以了
//2.初始化
if(nums[0]<0)
{
f[0]=0;
g[0]=1;
}
else if(nums[0]>0)
{
f[0]=1;
g[0]=0;
}
else
{
f[0]=0;
g[0]=0;
}
if(n==1) return f[0];
//3.填表
for(int i=1;i<n;i++)
{
if(nums[i]>0)
{
f[i]=f[i-1]+1;
g[i]=(g[i-1]==0 ? 0 : g[i-1]+1);
}
else if(nums[i]<0)
{
f[i]=(g[i-1]==0 ? 0 : g[i-1]+1);
g[i]=f[i-1]+1;
}
}
//4.返回值
int fmax=INT_MIN;
for(int x:f) fmax=max(fmax,x);
return fmax;
}
};
5.等差数列划分
class Solution
{
public:
int numberOfArithmeticSlices(vector<int>& nums)
{
//1.创建dp表
int n=nums.size();
if(n==1 || n==2) return 0;
vector<int> dp(n);
//2.初始化
dp[0]=0;
dp[1]=0;
//3.填表
int sum=0;
for(int i=2;i<n;i++)
{
int a=nums[i-2],b=nums[i-1],c=nums[i];
if(a-b == b-c)
{
dp[i]=dp[i-1]+1;
}
else
{
dp[i]=0;
}
sum += dp[i];
}
//4.返回值
return sum;
}
};
6.最长湍流子数组
class Solution
{
public:
int maxTurbulenceSize(vector<int>& nums)
{
//1.创建dp表
int n=nums.size();
if(n==1) return 1;
vector<int> f(n,1); //表示以i为结尾,并且呈现上升趋势的最大子数组长度
vector<int> g(n,1); //表示以i为结尾,并且呈现下降趋势的最大子数组长度
//2.初始化
//3.填表
int fmax=INT_MIN,gmax=INT_MIN;
for(int i=1;i<n;i++)
{
if(nums[i-1]<nums[i]) f[i]=g[i-1]+1;
if(nums[i-1]>nums[i]) g[i]=f[i-1]+1;
fmax=max(f[i],fmax);
gmax=max(g[i],gmax);
}
//4.返回值
return max(fmax,gmax);
}
};
7.单词拆分
class Solution
{
public:
bool wordBreak(string s, vector<string>& wordDict)
{
//优化
unordered_set<string> hash;
for(auto x:wordDict) hash.insert(x);
//1.创建dp表
int n=s.size();
vector<bool> dp(n+1); //这次用的bool类型的dp表
//2.初始化
dp[0]=true; //保证后续填表正确
s=' '+s; //使原始字符串下标统一加一
//3.填表
for(int i=1;i<=n;i++)
{
for(int j=i;j>=1;j--) //最后一个单词的起始位置
{
if(dp[j-1] && hash.count(s.substr(j,i-j+1))) //如果满足前i-1在字典中,最后一个单词在字典中
{
dp[i]=true;
break;
}
}
}
//4.返回值
return dp[n];
}
};
8.环绕字符串中唯⼀的⼦字符串
467. 环绕字符串中唯一的子字符串 - 力扣(LeetCode)
class Solution
{
public:
int findSubstringInWraproundString(string s)
{
//1.创建dp表
int n=s.size();
vector<int> dp(n,1);
//2.初始化
//3.填表
for(int i=1;i<n;i++)
{
if(s[i]==s[i-1]+1 || (s[i-1]=='z'&&s[i]=='a'))
{
dp[i] += dp[i-1];
}
}
//4.返回值
int hash[26]={0};
for(int i=0;i<n;i++)
{
hash[s[i]-'a'] = max(hash[s[i]-'a'],dp[i]); //dp[i]对应的是哪个字符没有影响,只要有hash[s[i]-'a']限制在26个字母范围内就行
}
int sum=0;
for(auto x:hash) sum += x;
return sum;
}
};
五.子序列问题
1.最长递增子序列
class Solution
{
public:
int lengthOfLIS(vector<int>& nums)
{
//1.创建dp表
int n = nums.size();
if(n==1) return 1;
vector<int> dp(n,1);
//2.初始化
//3.填表
int ans = 0;
for (int i = 1; i < n; i++)
{
for (int j = 0; j < i; j++)
{
if (nums[j] < nums[i])
{
dp[i] = max(dp[i], dp[j] + 1);
}
}
ans = max(ans, dp[i]);
}
//4.返回值
return ans;
}
};
2.摆动序列
class Solution
{
public:
int wiggleMaxLength(vector<int>& nums)
{
//1.创建dp表
int n=nums.size();
if(n==1) return 1;
vector<int> f(n,1);
vector<int> g(n,1);
//2.初始化
//3.填表
int fmax=INT_MIN,gmax=INT_MIN;
for(int i=1;i<n;i++)
{
for(int j=0;j<i;j++)
{
if(nums[i]>nums[j])
{
f[i]=max(f[i],g[j]+1);
}
if(nums[i]<nums[j])
{
g[i]=max(g[i],f[j]+1);
}
}
fmax=max(fmax,f[i]);
gmax=max(gmax,g[i]);
}
//4.返回值
return max(fmax,gmax);
}
};
3.最长递增子序列的个数
673. 最长递增子序列的个数 - 力扣(LeetCode)
class Solution
{
public:
int findNumberOfLIS(vector<int>& nums)
{
//1.创建dp表
int n=nums.size();
vector<int> len(n,1),count(n,1);
//2.初始化
//3.填表
int maxlen=1,maxcount=1;
for(int i=1;i<n;i++)
{
for(int j=0;j<i;j++)
{
if(nums[j]<nums[i]) //保证递增
{
if(len[j]+1 == len[i]) count[i] += count[j];
else if(len[j]+1 > len[i])
{
len[i]=len[j]+1;
count[i]=count[j];
}
}
}
if(maxlen==len[i]) maxcount += count[i];
else if(maxlen < len[i])
{
maxlen = len[i]; //改变最大值
maxcount = count[i]; //并且重新计数
}
}
//4.返回值
return maxcount;
}
};
4.最长数对链
class Solution
{
public:
int findLongestChain(vector<vector<int>>& pairs)
{
//预处理
sort(pairs.begin(),pairs.end());
//1.创建dp表
int n=pairs.size();
vector<int> dp(n,1);
//2.初始化
//3.填表
int maxdp=1;
for(int i=1;i<n;i++)
{
for(int j=0;j<i;j++)
{
if(pairs[j][1] < pairs[i][0]) //要先保证后面的数对的第一个值大于前一个数对第二个值
{
dp[i]=max(dp[i],dp[j]+1);
}
}
maxdp=max(maxdp,dp[i]);
}
//4.返回值
return maxdp;
}
};
5.最长定差子序列
按照之前的解法:超出时间限制
class Solution
{
public:
int longestSubsequence(vector<int>& arr, int difference)
{
//1.创建dp表
int n=arr.size();
if(n==1) return 1;
vector<int> dp(n,1);
//2.初始化
//3.填表
int maxdp=1;
for(int i=1;i<n;i++)
{
for(int j=0;j<i;j++)
{
if(arr[i]-arr[j]==difference)
{
dp[i]=max(dp[i],dp[j]+1);
}
}
maxdp=max(maxdp,dp[i]);
}
//4.返回值
return maxdp;
}
};
class Solution
{
public:
int longestSubsequence(vector<int>& arr, int difference)
{
//1.创建dp表
int n=arr.size();
if(n==1) return 1;
unordered_map<int,int> hash;
//2.初始化
hash[arr[0]]=1;
//3.填表
int maxdp=1;
for(int i=1;i<n;i++)
{
hash[arr[i]]=hash[arr[i]-difference]+1;
maxdp=max(maxdp,hash[arr[i]]);
}
//4.返回值
return maxdp;
}
};
6.最长斐波那契子序列的长度
873. 最长的斐波那契子序列的长度 - 力扣(LeetCode)
class Solution
{
public:
int lenLongestFibSubseq(vector<int>& arr)
{
//1.创建dp表
int n=arr.size();
unordered_map<int,int> hash;
for(int i=0;i<n;i++) hash[arr[i]]=i;
vector<vector<int>> dp(n,vector<int>(n,2)); //创建2维dp表,靠ij来固定住k
//2.初始化
//3.填表
int dpmax=2;
for(int j=2;j<n;j++) //固定c
{
for(int i=1;i<j;i++) //固定b
{
int c=arr[j],b=arr[i],a=arr[j]-arr[i];
if(a<arr[i] && hash.count(a)) //a存在,而且比b小
{
int k=hash[a]; //k为a对应的下标
dp[i][j]=dp[k][i]+1;
}
dpmax=max(dpmax,dp[i][j]);
}
}
//4.返回值
return dpmax<3 ? 0 :dpmax;
}
};
7.最长等差数列
class Solution
{
public:
int longestArithSeqLength(vector<int>& nums)
{
//1.创建dp表
unordered_map<int,int> hash; //元素和下标对应起来
hash[nums[0]]=0;
int n=nums.size();
vector<vector<int>> dp(n,vector<int>(n,2));
//2.初始化
//3.填表
int dpmax=2;
for(int i=1;i<n;i++) //固定倒数第二个数,枚举最后一个数
{
for(int j=i+1;j<n;j++)
{
int b=nums[i],c=nums[j];
int a=2*b-c;
if(hash.count(a))
{
int k=hash[a];
dp[i][j]=dp[k][i]+1;
}
dpmax=max(dpmax,dp[i][j]);
}
hash[nums[i]]=i; //边走边填,减少hash.count()的时间消耗
}
//4.返回值
return dpmax;
}
};
8.等差序列划分II
446. 等差数列划分 II - 子序列 - 力扣(LeetCode)
class Solution
{
public:
int numberOfArithmeticSlices(vector<int>& nums)
{
int n=nums.size();
//优化
unordered_map<long long,vector<int>> hash; //存元素与对应的下标数组
for(int i=0;i<n;i++)
{
hash[nums[i]].push_back(i);
}
//1.创建dp表
vector<vector<int>> dp(n,vector<int>(n,0));
//2.初始化
//3.填表
int sum=0;
for(int j=2;j<n;j++)
{
for(int i=1;i<j;i++)
{
long long a=(long long)2*nums[i]-nums[j];
if(hash.count(a))
{
for(auto k:hash[a])
{
if(k<i) dp[i][j] += dp[k][i]+1;
}
}
sum += dp[i][j];
}
}
//4.返回值
return sum;
}
};
六.回文串问题
1.回文子串
class Solution
{
public:
int countSubstrings(string s)
{
//1.创建dp表
int n=s.size();
vector<vector<bool>> dp(n,vector<bool>(n,false));
//2.初始化
//3.填表
int sum=0;
for(int j=0;j<n;j++)
{
for(int i=0;i<=j;i++)
{
if(s[i]==s[j])
{
if(i==j) dp[i][j]=true;
else if(i+1==j) dp[i][j]=true;
else
{
dp[i][j]=dp[i+1][j-1];
}
}
if(dp[i][j]) sum++;
}
}
//4.返回值
return sum;
}
};
2.最长回文子串
class Solution
{
public:
string longestPalindrome(string s)
{
//1.创建dp表
int n=s.size();
if(n==1) return s;
vector<vector<bool>> dp(n,vector<bool>(n,false));
//2.初始化
//3.填表
int maxlen=0,maxi=0;
for(int j=0;j<n;j++)
{
for(int i=0;i<=j;i++)
{
if(s[i]==s[j])
{
if(i==j) dp[i][j]=true;
else if(i+1==j) dp[i][j]=true;
else
{
dp[i][j]=dp[i+1][j-1];
}
}
if(dp[i][j] && (j-i+1>maxlen))
{
maxlen=j-i+1;
maxi=i;
}
}
}
//4.返回值
return s.substr(maxi,maxlen);
}
};
3.回文串分割IV
class Solution
{
public:
bool checkPartitioning(string s)
{
int n=s.size();
//1.创建dp表
vector<vector<bool>> dp(n,vector<bool>(n,false));
//2.初始化
//3.填表
for(int j=0;j<n;j++)
{
for(int i=0;i<=j;i++)
{
if(s[i]==s[j])
{
if(i==j) dp[i][j]=true;
else if(i+1==j) dp[i][j]=true;
else
{
dp[i][j]=dp[i+1][j-1];
}
}
}
}
//4.返回值
for(int i=0;i<n;i++)
{
for(int j=i+2;j<n;j++)
{
//分成了三段:[0,i][i+1,j-1][j,n-1]
if(dp[0][i] && dp[i+1][j-1] && dp[j][n-1]) return true;
}
}
return false;
}
};
4.回文串分割II
class Solution
{
public:
int minCut(string s)
{
//预处理
int n=s.size();
vector<vector<bool>> isback(n,vector<bool>(n,false));
for(int j=0;j<n;j++)
{
for(int i=0;i<=j;i++)
{
if(s[i]==s[j])
{
if(i==j) isback[i][j]=true;
else if(i+1==j) isback[i][j]=true;
else isback[i][j]=isback[i+1][j-1];
}
}
}
//1.创建dp表
vector<int> dp(n,INT_MAX);
//2.初始化
//3.填表
for(int i=0;i<n;i++)
{
if(isback[0][i]) dp[i]=0; //0到i是回文串,不需要切了
else
{
for(int j=0;j<=i;j++)
{
if(isback[j][i]) //如果j到i是回文串
{
dp[i]=min(dp[i],dp[j-1]+1);
}
}
}
}
//4.返回值
return dp[n-1];
}
};
5.最长回文子序列
class Solution
{
public:
int longestPalindromeSubseq(string s)
{
//1.创建dp表
int n=s.size();
vector<vector<int>> dp(n,vector<int>(n,0)); //dp[i][j]表示ij区间内的最长回文串序列的长度
//2.初始化
//3.填表
for(int j=0;j<n;j++)
{
for(int i=j;i>=0;i--)
{
if(s[i]==s[j])
{
if(i==j) dp[i][j]=1;
else if(i+1==j) dp[i][j]=2;
else dp[i][j]=dp[i+1][j-1]+2;
}
else
{
dp[i][j]=max(dp[i][j-1],dp[i+1][j]); //因为填dp[i][j]需要用到dp[i+1][j](下面那一行的数),所以从下往上填表
}
}
}
//4.返回值
return dp[0][n-1];
}
};
6.让字符串成为回⽂串的最⼩插⼊次数
1312. 让字符串成为回文串的最少插入次数 - 力扣(LeetCode)
class Solution
{
public:
int minInsertions(string s)
{
//1.创建dp表
int n=s.size();
vector<vector<int>> dp(n,vector<int>(n,0)); //表示ij区间内的最少次数
//2.初始化
//3.填表
for(int j=0;j<n;j++)
{
for(int i=j;i>=0;i--)
{
if(s[i]==s[j])
{
if(i==j) dp[i][j]=0;
else if(i+1==j) dp[i][j]=0;
else dp[i][j]=dp[i+1][j-1];
}
else
{
dp[i][j]=min(1+dp[i][j-1],1+dp[i+1][j]); //最小切割次数
}
}
}
//4.返回值
return dp[0][n-1];
}
};
七.两个数组的 dp 问题
1.最长公共子序列
class Solution
{
public:
int longestCommonSubsequence(string text1, string text2)
{
//1.创建dp表
int m=text1.size(),n=text2.size();
vector<vector<int>> dp(m+1,vector<int>(n+1,0)); //表示[0,i]与[0,j]区段上的最长公共子序列长度
//2.初始化
text1=" "+text1;
text2=" "+text2;
//3.填表
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(text1[i]==text2[j])
{
dp[i][j]=dp[i-1][j-1]+1;
}
else
{
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
}
}
}
//4.返回值
return dp[m][n];
}
};
2.不相交的线
类似于最长公共子序列
class Solution
{
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2)
{
//1.创建dp表
int m=nums1.size(),n=nums2.size();
vector<vector<int>> dp(m+1,vector<int>(n+1)); //表示的是[0,i]与[0,j]的最长公共子序列长度
//2.初始化
//3.填表
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;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]);
}
}
}
//4.返回值
return dp[m][n];
}
};
3.不同的子序列
class Solution
{
public:
int numDistinct(string s, string t)
{
//1.创建dp表
int m=t.size(),n=s.size();
vector<vector<double>> dp(m+1,vector<double>(n+1)); //表示s的0到j区间内的所有子序列,t的0到i区间内,子串的个数
//2.初始化
for(int j=0;j<n;j++) dp[0][j]=1; //当t为空串时,s中一定有一个子串
for(int i=1;i<m;i++) dp[i][0]=0; //当s为空串时且t不为空串,一定没有子串
//3.填表
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j] += dp[i][j-1];
if(s[j-1]==t[i-1])
{
dp[i][j] += dp[i-1][j-1];
}
}
}
//4.返回值
return dp[m][n];
}
};
4.通配符匹配
class Solution
{
public:
bool isMatch(string s, string p)
{
//1.创建dp表
int m=s.size(),n=p.size();
s=" "+s;
p=" "+p;
vector<vector<bool>> dp(m+1,vector<bool>(n+1,false)); //表示s的0-i能不能与p的0-j匹配上
//2.初始化
dp[0][0]=true;
for(int j=1;j<=n;j++)
{
if(p[j]=='*') dp[0][j]=true;
else break;
}
//3.填表
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(p[j]=='*')
{
dp[i][j]=(dp[i-1][j] || dp[i][j-1]);
}
else
{
dp[i][j]=(p[j]==s[i] || p[j]=='?') && (dp[i-1][j-1]);
}
}
}
//4.返回值
return dp[m][n];
}
};
5.正则表达式匹配
6.交错字符串
class Solution
{
public:
bool isInterleave(string s1, string s2, string s3)
{
//1.创建dp表
int m=s1.size(),n=s2.size(),s=s3.size();
if(s!=m+n) return false;
vector<vector<bool>> dp(m+1,vector<bool>(n+1,false)); //dp表示s1从1-i,s2从1-j,能不能构成s3的1-(i+j)
//2.初始化
s1=" "+s1,s2=" "+s2,s3=" "+s3;
if(s==0) dp[0][0]=true;
for(int j=1;j<=n;j++)
{
if(s2[j]==s3[j]) dp[0][j]=true;
else break;
}
for(int i=1;i<=m;i++)
{
if(s1[i]==s3[i]) dp[i][0]=true;
else break;
}
//3.填表
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(s1[i]==s3[i+j] && dp[i-1][j]) dp[i][j]=true;
else if(s2[j]==s3[i+j] && dp[i][j-1]) dp[i][j]=true;
}
}
//4.返回值
return dp[m][n];
}
};
7.两个字符串的最⼩ ASCII 删除和
712. 两个字符串的最小ASCII删除和 - 力扣(LeetCode)
class Solution
{
public:
int minimumDeleteSum(string s1, string s2)
{
//1.创建dp表
int m=s1.size(),n=s2.size();
vector<vector<int>> dp(m+1,vector<int>(n+1,0)); //表示s1的1-i与s2的1-j的最长公共子序列的最大acsii码和
//2.初始化
//3.填表
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if(s1[i-1]==s2[j-1])
{
dp[i][j]=max(dp[i][j],dp[i-1][j-1]+s1[i-1]);
}
}
}
//4.返回值
int sum=0;
for(auto x:s1) sum += x;
for(auto x:s2) sum += x;
return sum-dp[m][n]*2;
}
};
8.最长重复子数组
class Solution
{
public:
int findLength(vector<int>& nums1, vector<int>& nums2)
{
//1.创建dp表
int m=nums1.size(),n=nums2.size();
vector<vector<int>> dp(m+1,vector<int>(n+1,0)); //表示num1以i结尾,num2以j结尾的所有子数组的最大公共长度
//2.初始化
//3.填表
int dpmax=0;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(nums1[i-1]==nums2[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
}
dpmax=max(dpmax,dp[i][j]);
}
}
//4.返回值
return dpmax;
}
};
八.01背包问题
1.01背包
#include <iostream>
#include <string.h>
using namespace std;
const int N=1001;
int n,V;
int v[N],w[N];
int dp[N][N];
int main()
{
cin>>n>>V;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
//第一题 dp表示从前i个物品挑选,总体积不超过j,选出的最大价值
dp[N][N]={0};
for(int i=1;i<=n;i++)
{
for(int j=1;j<=V;j++)
{
dp[i][j]=dp[i-1][j];
if(j-v[i]>=0) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
}
}
cout<<dp[n][V]<<endl;
//第一题 dp表示从前i个物品挑选,总体积等于j,选出的最大价值
memset(dp,0,sizeof(dp));
for(int j=1;j<=V;j++) dp[0][j]=-1; //初始化
for(int i=1;i<=n;i++)
{
for(int j=1;j<=V;j++)
{
dp[i][j]=dp[i-1][j];
if(j-v[i]>=0 && dp[i-1][j-v[i]]!=-1)
dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
}
}
cout<<(dp[n][V]==-1 ? 0 : dp[n][V])<<endl;
return 0;
}
#include <iostream>
#include <string.h>
using namespace std;
const int N=1001;
int n,V;
int v[N],w[N];
int dp[N];
int main()
{
cin>>n>>V;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
//第一题 dp表示从前i个物品挑选,总体积不超过j,选出的最大价值
dp[N]={0};
for(int i=1;i<=n;i++)
{
for(int j=V;j-v[i]>=0;j--)
{
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[V]<<endl;
//第一题 dp表示从前i个物品挑选,总体积等于j,选出的最大价值
memset(dp,0,sizeof(dp));
for(int j=1;j<=V;j++) dp[0][j]=-1; //初始化
for(int i=1;i<=n;i++)
{
for(int j=V;j-v[i]>=0;j--)
{
if(dp[i-1][j-v[i]]!=-1)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<(dp[V]==-1 ? 0 : dp[V])<<endl;
return 0;
}
2.分割等和子集
class Solution
{
public:
bool canPartition(vector<int>& nums)
{
//1.创建dp表
int n=nums.size();
int sum=0;
for(auto x:nums) sum += x;
if(sum%2 == 1) return false;
int aim=sum/2; //注意dp的长度是aim+1,不是n+1
vector<vector<bool>> dp(n+1,vector<bool>(aim+1,false)); //表示前i个数,能否凑成和为j
//2.初始化
for(int i=0;i<=n;i++) dp[i][0]=true;
//3.填表
for(int i=1;i<=n;i++)
{
for(int j=1;j<=aim;j++) //注意dp的长度是
{
dp[i][j]=dp[i-1][j];
if(j-nums[i-1]>=0) dp[i][j]= dp[i][j] || dp[i-1][j-nums[i-1]]; //注意nums[i-1],与原数组的对应关系
}
}
//4.返回值
return dp[n][sum/2];
}
};
优化:去掉行遍历,只留一行 ; 注意从右往左遍历
class Solution
{
public:
bool canPartition(vector<int>& nums)
{
//1.创建dp表
int n=nums.size();
int sum=0;
for(auto x:nums) sum += x;
if(sum%2 == 1) return false;
int aim=sum/2;
vector<bool> dp(aim+1); //表示前i个数,能否凑成和为j
//2.初始化
dp[0]=true;
//3.填表
for(int i=1;i<=n;i++)
{
for(int j=aim;j>=nums[i-1];j--) //注意dp的长度是
{
dp[j] = dp[j] || dp[j-nums[i-1]]; //注意nums[i-1],与原数组的对应关系
}
}
//4.返回值
return dp[aim];
}
};
3.目标和
class Solution
{
public:
int findTargetSumWays(vector<int>& nums, int target)
{
//预处理
int sum=0,a=0;
for(auto x:nums) sum += x;
a=(sum+target)/2;
if(a<0 || (sum+target)%2!=0) return 0;
//1.创建dp表
int n=nums.size();
vector<vector<int>> dp(n+1,vector<int>(a+1)); //表示从0-i,和为j,共有几种选法
//2.初始化
dp[0][0]=1;
//3.填表
for(int i=1;i<=n;i++)
{
for(int j=0;j<=a;j++) //因为第一例没初始化,所以j要从0开始
{
dp[i][j] += dp[i-1][j];
if(j-nums[i-1]>=0) dp[i][j] += dp[i-1][j-nums[i-1]]; //注意多加了一行一列,要减一
}
}
//4.返回值
return dp[n][a];
}
};
优化
class Solution
{
public:
int findTargetSumWays(vector<int>& nums, int target)
{
//预处理
int sum=0,a=0;
for(auto x:nums) sum += x;
a=(sum+target)/2;
if(a<0 || (sum+target)%2!=0) return 0;
//1.创建dp表
int n=nums.size();
vector<int> dp(a+1);
//2.初始化
dp[0]=1;
//3.填表
for(int i=1;i<=n;i++)
{
for(int j=a;j>=nums[i-1];j--) //因为第一例没初始化,所以j要从0开始
{
dp[j] += dp[j-nums[i-1]];
}
}
//4.返回值
return dp[a];
}
};
4.最后一块石头的重量II
1049. 最后一块石头的重量 II - 力扣(LeetCode)
class Solution
{
public:
int lastStoneWeightII(vector<int>& nums)
{
//1.创建dp表
int sum=0;
for(auto x:nums) sum += x;
int n=nums.size();
int aim=sum/2;
vector<vector<int>> dp(n+1,vector<int>(aim+1)); //表示从0-i,总和不超过j,选的最大值
//2.初始化
//3.填表
for(int i=1;i<=n;i++)
{
for(int j=0;j<=aim;j++)
{
dp[i][j]=dp[i-1][j];
if(j-nums[i-1]>=0) dp[i][j]=max(dp[i][j],dp[i-1][j-nums[i-1]]+nums[i-1]);
}
}
//4.返回值
int positive = dp[n][aim];
int negative = sum-dp[n][aim];
return abs(negative-positive);
}
};
九.完全背包
1.完全背包
#include<iostream>
#include<string.h>
using namespace std;
const int N=1001;
int n,V,v[N],w[N];
int dp[N][N];
int main()
{
cin>>n>>V;
for(int i=1;i<=N;i++) cin>>v[i]>>w[i];
//第一问
for(int i=1;i<=n;i++)
{
for(int j=0;j<=V;j++)
{
dp[i][j]=dp[i-1][j];
if(j>=v[i]) dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]); //完全背包这里是dp[i][j-v[i]]
}
}
cout<<dp[n][V]<<endl;
//第二问
memset(dp,0,sizeof(dp));
for(int j=0;j<=V;j++) dp[0][j]=-1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=V;j++)
{
dp[i][j]=dp[i-1][j];
if(j>=v[i] && dp[i][j-v[i]]!=-1) dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
}
}
if(dp[n][V] == -1) cout<<0<<endl;
else cout<< dp[n][V]<<endl;
return 0;
}
int main()
{
cin>>n>>V;
for(int i=1;i<=N;i++) cin>>v[i]>>w[i];
//第一问
for(int i=1;i<=n;i++)
{
for(int j=v[i];j<=V;j++)
{
dp[j]=max(dp[j],dp[j-v[i]]+w[i]); //完全背包这里是dp[i][j-v[i]]
}
}
cout<<dp[V]<<endl;
//第二问
memset(dp,0,sizeof(dp));
for(int j=0;j<=V;j++) dp[j]=-1;
for(int i=1;i<=n;i++)
{
for(int j==v[i];j<=V;j++)
{
if(dp[j-v[i]]!=-1) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
if(dp[V] == -1) cout<<0<<endl;
else cout<< dp[V]<<endl;
return 0;
}
2.零钱兑换
class Solution
{
public:
int coinChange(vector<int>& coins, int amount)
{
const int Max = 0x3f3f3f3f;
//1.创建dp表
int n=coins.size();
vector<vector<int>> dp(n+1,vector<int>(amount+1));
//2.初始化
for(int j=1;j<=amount;j++) dp[0][j]=Max;
//3.填表
for(int i=1;i<=n;i++)
{
for(int j=0;j<=amount;j++)
{
dp[i][j]=dp[i-1][j];
if(j-coins[i-1]>=0) dp[i][j]=min(dp[i][j],dp[i][j-coins[i-1]]+1);
}
}
//4.返回值
return (dp[n][amount] >= Max ? -1 : dp[n][amount]);
}
};
3.零钱兑换II
class Solution
{
public:
int change(int amount, vector<int>& coins)
{
int n=coins.size();
//1.创建dp表
vector<vector<int>> dp(n+1,vector<int>(amount+1));
//2.初始化
dp[0][0]=1;
//3.填表
for(int i=1;i<=n;i++)
{
for(int j=0;j<=amount;j++)
{
dp[i][j] += dp[i-1][j];
if(j-coins[i-1]>=0) dp[i][j] += dp[i][j-coins[i-1]];
}
}
//4.返回值
return dp[n][amount]==-1 ? 0 : dp[n][amount];
}
};
4.完全平方数
class Solution
{
public:
int numSquares(int n)
{
const int Max=0x3f3f3f3f;
vector<int> nums;
int k=0;
while(k*k<=n)
{
nums.push_back(k*k);
k++;
}
int len=nums.size();
//1.创建dp表
vector<vector<int>> dp(len+1,vector<int>(n+1));
//2.初始化
for(int j=1;j<=n;j++) dp[0][j]=Max;
//3.填表
for(int i=1;i<=len;i++)
{
for(int j=0;j<=n;j++)
{
dp[i][j]=dp[i-1][j];
if(j-nums[i-1]>=0) dp[i][j]=min(dp[i][j],dp[i][j-nums[i-1]]+1);
}
}
//4.返回值
return dp[len][n];
}
};
十.二维费用的背包问题(多了一个限制)
1.一和零
class Solution
{
public:
int findMaxForm(vector<string>& strs, int m, int n)
{
//1.创建dp表
int len=strs.size();
vector<vector<vector<int>>> dp(len+1,vector<vector<int>>(m+1,vector<int>(n+1)));
//2.初始化
//3.填表
for(int i=1;i<=len;i++)
{
//统计一下字符串中01的个数
int a=0,b=0;
for(auto ch:strs[i-1]) //别忘了下标与原数组的对应关系
{
if(ch=='0') a++;
else b++;
}
for(int j=0;j<=m;j++)
{
for(int k=0;k<=n;k++)
{
dp[i][j][k]=dp[i-1][j][k];
if(j-a>=0 && k-b>=0) dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-a][k-b]+1);
}
}
}
//4.返回值
return dp[len][m][n];
}
};
类比01背包的优化
class Solution
{
public:
int findMaxForm(vector<string>& strs, int m, int n)
{
//1.创建dp表
int len=strs.size();
vector<vector<int>> dp(m+1,vector<int>(n+1));
//2.初始化
//3.填表
for(int i=1;i<=len;i++)
{
//统计一下字符串中01的个数
int a=0,b=0;
for(auto ch:strs[i-1]) //别忘了下标与原数组的对应关系
{
if(ch=='0') a++;
else b++;
}
for(int j=m;j>=a;j--) //遍历顺序也要改
{
for(int k=n;k>=b;k--)
{
dp[j][k]=max(dp[j][k],dp[j-a][k-b]+1);
}
}
}
//4.返回值
return dp[m][n];
}
};
2.盈利计划
class Solution
{
public:
int profitableSchemes(int n, int m, vector<int>& group, vector<int>& profit)
{
const int MOD=1e9+7;
//1.创建dp表
int len=group.size();
vector<vector<vector<int>>> dp(len+1,vector<vector<int>>(n+1,vector<int>(m+1)));
//2.初始化
for(int j=0;j<=n;j++) dp[0][j][0]=1;
//3.填表
for(int i=1;i<=len;i++)
{
for(int j=0;j<=n;j++)
{
for(int k=0;k<=m;k++)
{
dp[i][j][k] += dp[i-1][j][k];
if(j-group[i-1]>=0) dp[i][j][k] += dp[i-1][j-group[i-1]][max(k-profit[i-1],0)];
dp[i][j][k] %= MOD;
}
}
}
//4.返回值
return dp[len][n][m];
}
};
十一.似包非包
1.组合总和IV
LCR 104. 组合总和 Ⅳ - 力扣(LeetCode)
class Solution
{
public:
int combinationSum4(vector<int>& nums, int target)
{
//1.创建dp表
vector<double> dp(target+1); //dp表示凑成i这个元素,需要的排列数个数
//2.初始化
dp[0]=1;
//3.填表
for(int i=1;i<=target;i++)
{
for(auto x:nums)
{
if(i-x>=0) dp[i] += dp[i-x];
}
}
//4.返回值
return dp[target];
}
};
2.不同的二叉搜索树
class Solution
{
public:
int numTrees(int n)
{
//1.创建dp表
vector<int> dp(n+1); //表示一组中节点个数为i时,共有多少种组合
//2.初始化
dp[0]=1;
//3.填表
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
dp[i] += dp[j-1]*dp[i-j];
}
}
//4.返回值
return dp[n];
}
};