从集合的角度来考虑dp问题
寻找某一状态来代表一类东西
dp考虑方式:1.状态表示 f[i] :(1)集合:所有以i结尾的子段(2) 属性max/min/数量。
2.状态计算——集合的划分。
f[i]=max(0,f[i-1])+nums[i];
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res=INT_MIN,last=0;
for(int i=0;i<nums.size();i++)
{
int now=max(0,last)+nums[i];
res=max(res,now);
last=now;
}
return res;
}
};
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int n=triangle.size();
vector<int> f(triangle[n-1].begin(),triangle[n-1].end()),g(n);
for(int i=n-2;i>=0;i--)
{
for(int j=0;j<=i;j++)
g[j]=min(f[j],f[j+1])+triangle[i][j];
f=g;
}
return f[0];
}
};
class Solution {
public:
int minimumTotal(vector<vector<int>>& nums) {
int n=nums.size();
vector<vector<long long>> f(2,vector<long long>(n));
f[0][0]=nums[0][0];
for(int i=1;i<n;i++)
{
for(int j=0;j<=i;j++)
{
f[i&1][j]=INT_MAX;
if(j>0) f[i&1][j]=min(f[i&1][j],f[i-1 & 1][j-1]+nums[i][j]);
if(j<i) f[i&1][j]=min(f[i&1][j],f[i-1 & 1][j]+nums[i][j]);
}
}
long long res=INT_MAX;
for(int i=0;i<n;i++)
res=min(res,f[n-1&1][i]);
return res;
}
};
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& g) {
int n=g.size(),m=g[0].size();
vector<vector<long long>> f(n,vector<long long>(m));
f[0][0]= !g[0][0];
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(g[i][j]) continue;
if(i>0) f[i][j]+=f[i-1][j];
if(j>0) f[i][j]+=f[i][j-1];
}
}
return f[n-1][m-1];
}
};
线性dp
区间dp
背包dp
f[i]表示前i个数的组合情况
s[i-1]表实前i个数中最后一个数
f[0]=1是设置的边界,这个边界非常巧妙,让你在算s[0]和s[0]s[1]时可得到f[2]=2,而且无需判断字长有没有2。这样在算所以f[1]+=f[0],(实际上在算是s[0]这个点)。可以这么认为在一个i长的字符串中,确定前i-1(或i-2)个就确定了全部.
class Solution {
public:
int numDecodings(string s) {
n=s.size();
vector<int> f(n+1);
f[0]=1;
for(int i=1;i<n;i++)
{
if(s[i-1]!='0')
f[i]+=f[i-1];
if(i>=2)
{
int a=(s[i-2]-'0')*10+s[i-1]-'0';
if(a>=10 && a<= 26)
f[i]+=f[i-2];
}
}
return f[n];
}
};
class Solution {
public:
int rob(int[] nums) {
int n=nums.size();
vector<int> f(n+1),g(n+1);
for(int i=1;i<=n;i++)
{
f[i]=max(f[i-1],g[i-1]);
g[i]=f[i-1]+nums[i-1];
}
return max(f[n],g[n]);
}
};
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入:[10,9,2,5,3,7,101,18]
输出: 4 解释: 最长的上升子序列是[2,3,7,101],
它的长度是4
。
说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 你算法的时间复杂度应该为 O(n2) 。
集合:所有以i结尾的上升子序列
属性:长度的最大值
状态计算:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n=nums.size();
vector<int> f(n);//
for(int i=0;i<n;i++)
{
f[i]=1;
for(int j=0;j<i;j++)
if(nums[i]>nums[j])
f[i]=max(f[i],f[j]+1);
}
int res=0;
for(int i=0;i<n;i++)
res=max(f[i],res);
return res;
}
};
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
集合:所有将第一个字符串的前i个字母,变成第二个字符串的前j个字母的方案。
属性:min
状态计算:(考虑最后一步)
insert:f[i,j-1] +1;
delete: f[i-1,j]+1;
replace:(1)f[i-1,j-1]+1;(2)f[i-1,j-1]
class Solution {
public:
vector<vector<int>> f;
int minDistance(string word1, string word2) {
int n=word1.size(),m=word2.size();
f=vector<vector<int>>(n+1,vector<int>(m+1));
for(int i=0;i<=n;i++) f[i][0]=i;
for(int i=0;i<=m;i++) f[0][i]=i;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
f[i][j]=min(f[i-1][j],f[i][j-1])+1;
f[i][j]=min(f[i][j],f[i-1][j-1]+(word1[i-1]!=word2[j-1]));
}
}
return f[n][m];
}
};
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。
示例 3:
输入: amount = 10, coins = [10]
输出: 1
注意:
你可以假设:
- 0 <= amount (总金额) <= 5000
- 1 <= coin (硬币面额) <= 5000
- 硬币种类不超过 500 种
- 结果符合 32 位符号整数
集合:所有由前i种硬币凑出的总钱数等于j的凑法;
属性:数量
状态计算
f[i,j]=f[i-1,j]+f[i-1,j-c]+f[i-1,j-2c]+f[i-1,j-3c]+f[i-1,j-4c]+f[i-1,j-5c]+f[i-1,j-kc];
f[i,j-c]=f[i-1,j-c]+f[i-1,j-2c]+f[i-1,j-3c]+f[i-1,j-4c]+f[i-1,j-5c]+f[i-1,j-kc];
所以有:f[i,j]=f[i-1,j]+f[i,j-c];
变成一维数组:f[j]=f[j]+f[j-c];
class Solution {
public:
int change(int m, vector<int>& coins) {
int n=coins.size();
vector<int> f(m+1);
f[0]=1;
for(auto c:coins)
for(int j=c;j<=m;j++)
f[j]+=f[j-c];
return f[m];
}
};
有台奇怪的打印机有以下两个特殊要求:
- 打印机每次只能打印同一个字符序列。
- 每次可以在任意起始和结束位置打印新字符,并且会覆盖掉原来已有的字符。
给定一个只包含小写英文字母的字符串,你的任务是计算这个打印机打印它需要的最少次数。
示例 1:
输入: "aaabbb"
输出: 2
解释: 首先打印 "aaa" 然后打印 "bbb"。
示例 2:
输入: "aba"
输出: 2
解释: 首先打印 "aaa" 然后在第二个位置打印 "b" 覆盖掉原来的字符 'a'。
提示: 输入字符串的长度不会超过 100。
class Solution {
public:
int strangePrinter(string s) {
if(s.empty()) return 0;
int n=s.size();
vector<vector<int>> f(n+1,vector<int>(n+1));
for(int len=1;len<=n;len++)
{
for(int l=0;l+len-1<n;l++)
{
int r=l+len-1;
f[l][r]=f[l+1][r]+1;
for(int k=l+1;k<=r;k++)
{
if(s[k]==s[l])
f[l][r]=min(f[l][r],f[l][k-1]+f[k+1][r]);
}
}
}
return f[0][n-1];
}
};
f[i,j]表示s的前i个字母和p的前j个字母是否匹配。
1.p[j]!=‘*’,f[i,j]=f[i-1,j-1] && (s[i]和p[j]匹配)