动态规划问题的特点
- 一般形式的求最值
- 核心问题是穷举,这类问题存在「重叠子问题」
- 具备「最优子结构」,即可以分解为很多子问题
- 需要列出正确的「状态转移方程」
- 一般的套路:明确 base case -> 明确「状态」-> 明确「选择」 -> 定义 dp 数组/函数的含义。
回文串系列题目
- 一般通过二维数组记录状态。
- 一般不能进行状态压缩。
- 状态转移方程s[i]==s[j]&&dp[left+1][right-1]。
- dp[i][j]表示下标从i到j的字符子串是否为回文串。
长度
给你一个字符串 s,找到 s 中最长的回文子串。
- 使用双指针遍历检测回文字符串。
- 当right-left<=2,即检测长度小于等于2时,不能直接用dp[left+1][right-1]判断
class Solution {
public:
string longestPalindrome(string s) {
int n=s.size();
int mlen=0;
int mleft=0;
vector<vector<int>> dp(n,vector<int>(n));
for(int right=0;right<n;right++){
for(int left=0;left<=right;left++){
if(s[left]==s[right]&&(right-left<=2||dp[left+1][right-1])){
dp[left][right]=1;
if(mlen<right-left+1){
mlen=right-left+1;
mleft=left;
}
}
}
}
return s.substr(mleft,mlen);
}
};
搜索从2到n的回文字符串
class Solution {
public:
string longestPalindrome(string s) {
int len=s.size();
if(len==0||len==1){
return s;
}
int dp[len][len]={0};
int start=0;
int max=1;
for(int i=0;i<len;i++){
dp[i][i]=1;
if(i<len-1&&s[i]==s[i+1]){
dp[i][i+1]=1;
start=i;
max=2;
}
}
for(int l=3;l<=len;l++){
for(int i=0;i+l-1<len;i++){
int j=i+l-1;
if(s[i]==s[j]&&dp[i+1][j-1]==1){
dp[i][j]=1;
start=i;
max=l;
}
}
}
return s.substr(start,max);
}
};
回文串数量
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
class Solution {
public:
int countSubstrings(string s) {
int n=s.size();
vector<vector<int>> dp(n,vector<int>(n));
int res=0;
for(int right=0;right<n;right++){
for(int left=0;left<=right;left++){
if(s[left]==s[right]&&(right-left<=2||dp[left+1][right-1])){
dp[left][right]=1;
res++;
}
}
}
return res;
}
};
分割回文串
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
- 首先计算出二维数组dp,深度优先遍历、回溯搜索dp,得出整个字符串所有分割方案。
class Solution {
public:
vector<vector<string>> partition(string s) {
int n=s.size();
vector<vector<string>> res;
if(n==0){
return res;
}
// dp
vector<vector<int>> dp(n,vector<int>(n));
for(int right=0;right<n;right++){
for(int left=0;left<=right;left++){
if(s[left]==s[right]&&(right-left<=2||dp[left+1][right-1])){
dp[left][right]=1;
}
}
}
// dfs
vector<string> path;
function<void(int)> dfs=[&](int index){
if(index==n){
res.emplace_back(path);
return;
}
for(int i=index;i<n;i++){
if(dp[index][i]){
path.emplace_back(s.substr(index,i-index+1));
dfs(i+1);
path.pop_back();
}
}
};
dfs(0);
return res;
}
};
斐波那契数
- 状态转移方程dp[i]=dp[i-1]+dp[i-2]
class Solution {
public:
int fib(int n) {
if(n==0)
return 0;
vector<int> dp(n+1);
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
- 类似上题
class Solution {
public int tribonacci(int n) {
if(n<3){
return n==0?0:1;
}
int n0=0,n1=1,n2=1;
for(int i=3;i<=n;i++){
int tmp=n0+n1+n2;
int tmp1=n1;
n1=n2;
n0=tmp1;
n2=tmp;
}
return n2;
}
}
图的点到点最大路径数
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m+1,vector(n+1,0));
dp[1][0]=1;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m][n];
}
};
子序和
最大和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int ans=nums[0];
int n=nums.size();
vector<int> dp(n);
dp[0]=nums[0];
for(int i=1;i<n;i++){
dp[i]=max(nums[i]+dp[i-1],nums[i]);
ans=max(dp[i],ans);
}
return ans;
}
};