文章目录
- 一、斐波那契数列模型
-
- [1. 第 N 个泰波那契数(详细拆解动态规划过程)](#1. 第 N 个泰波那契数(详细拆解动态规划过程))
- [2. 使用最小花费爬楼梯](#2. 使用最小花费爬楼梯)
- [3. 解码方法](#3. 解码方法)
- 二、路径问题
-
- [1. 不同路径(拆解路径问题的动态规划过程)](#1. 不同路径(拆解路径问题的动态规划过程))
- [2. 不同路径 II](#2. 不同路径 II)
- [3. 珠宝的最高价值](#3. 珠宝的最高价值)
- [4. 下降路径最小和](#4. 下降路径最小和)
- [5. 地下城游戏(与前4题的推导方法 不同)](#5. 地下城游戏(与前4题的推导方法 不同))
- [三、简单多状态 dp 问题](#三、简单多状态 dp 问题)
-
- [1. 按摩师](#1. 按摩师)
- [2. 打家劫舍 II](#2. 打家劫舍 II)
- [3. 删除并获得点数](#3. 删除并获得点数)
- [4. 粉刷房子](#4. 粉刷房子)
- [5. 买卖股票的最佳时机含冷冻期](#5. 买卖股票的最佳时机含冷冻期)
- 四、子数组、子串系列(数组中连续的一段)
-
- [1. 最大子数组和](#1. 最大子数组和)
- [2. 环形子数组的最大和](#2. 环形子数组的最大和)
- [3. 乘积最大子数组](#3. 乘积最大子数组)
- [4. 单词拆分](#4. 单词拆分)
- [5. 环绕字符串中唯一的子字符串](#5. 环绕字符串中唯一的子字符串)
- 五、子序列问题(数组中不连续的一段)
-
- [1. 最长递增子序列](#1. 最长递增子序列)
- [2. 摆动序列](#2. 摆动序列)
- [3. 最长递增子序列的个数](#3. 最长递增子序列的个数)
- [4. 最长定差子序列](#4. 最长定差子序列)
- [5. 最长等差数列(二维bp表)](#5. 最长等差数列(二维bp表))
- [6. 最长的斐波那契子序列的长度](#6. 最长的斐波那契子序列的长度)
- 六、回文子串问题
-
- [1. 回文子串(模板题)](#1. 回文子串(模板题))
- [2. 分割回文串 IV(hard)](#2. 分割回文串 IV(hard))
- [3. 分割回文串 II(hard)](#3. 分割回文串 II(hard))
- [4. 最长回文子序列](#4. 最长回文子序列)
- [七、两个数组的 dp(含字符串数组)](#七、两个数组的 dp(含字符串数组))
-
- [1. 最长公共子序列(模板题)](#1. 最长公共子序列(模板题))
- [2. 不同的子序列(hard)](#2. 不同的子序列(hard))
- [3. 交错字符串(子序列)](#3. 交错字符串(子序列))
- [4. 最长重复子数组(子数组连续,模板题)](#4. 最长重复子数组(子数组连续,模板题))
- 八、背包问题概述
-
- [1. 01背包(模板题)](#1. 01背包(模板题))
- [2. 分割等和子集](#2. 分割等和子集)
- [3. 目标和(特殊)](#3. 目标和(特殊))
- [4. 完全背包(模板题)](#4. 完全背包(模板题))
- [5. 完全平方数](#5. 完全平方数)
- [6. 一和零(二维限制条件的 01背包问题)](#6. 一和零(二维限制条件的 01背包问题))
- [7. 组合总和 Ⅳ(似包非包)](#7. 组合总和 Ⅳ(似包非包))
一、斐波那契数列模型
1. 第 N 个泰波那契数(详细拆解动态规划过程)
1137. 第 N 个泰波那契数
class Solution {
public:
int tribonacci(int n) {
if(n == 0)
return 0;
if(n == 1 || n == 2)
return 1;
vector<int> dp(n+1, 0); // dp表
// 初始化dp表
dp[0] = 0, dp[1] = 1, dp[2] = 1;
for(int i = 3; i <= n; i++)
dp[i] = dp[i-3] + dp[i-2] + dp[i-1]; // 状态转义方程
return dp[n];
}
};
2. 使用最小花费爬楼梯
746. 使用最小花费爬楼梯
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
vector<int> dp(n+1, 0);
// 初始化
dp[0] = 0, dp[1] = 0;
for(int i = 2; i <= n; i++)
{
// 状态转移方程
dp[i] = min(dp[i-2] + cost[i-2], dp[i-1] + cost[i-1]);
}
return dp[n];
}
};
3. 解码方法
91. 解码方法
class Solution {
public:
int numDecodings(string s)
{
int n = s.size();
vector<int> dp(n, 0);
// 初始化dp[0] 和 dp[1]
if(s[0] == '0')
return 0;
dp[0] = 1;
if(n == 1)
return dp[0];
dp[1] = 1;
if(s[1] == '0')
{
if(s[0] > '2')
return 0;
}
else
{
if((s[1] <= '6' && s[0] == '2') || s[0] == '1')
dp[1] = 2;
}
for(int i = 2; i < n; i++)
{
// 分情况使用 状态转移方程
if(s[i] != '0')
dp[i] += dp[i-1];
if((s[i] <= '6' && s[i-1] == '2') || s[i-1] == '1')
dp[i] += dp[i-2];
if(dp[i] == 0)
return 0;
}
return dp[n-1];
}
};
二、路径问题
1. 不同路径(拆解路径问题的动态规划过程)
62. 不同路径
class Solution {
public:
int uniquePaths(int m, int n) {
// 创建dp表时,多开辟一行一列作为虚拟节点
vector<vector<int>> dp(m+1, vector(n+1, 0));
// 初始化虚拟节点
dp[1][0] = 1;
for(int i = 1; i < m+1; i++)
{
for(int j = 1; j < n+1; j++)
dp[i][j] = dp[i-1][j] + dp[i][j-1]; // 状态转移方程
}
return dp[m][n];
}
};
2. 不同路径 II
63. 不同路径 II
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
// 创建dp表时,多开辟一行一列作为虚拟节点
vector<vector<long long>> dp(m+1, vector<long long>(n+1, 0));
// 初始化虚拟节点
dp[0][1] = 1;
for(int i = 1; i < dp.size(); i++)
{
for(int j = 1; j < dp[0].size(); j++)
{
if(obstacleGrid[i-1][j-1] == 0) // 对空位置进行 dp值计算
dp[i][j] = dp[i][j-1] + dp[i-1][j]; // 状态转移方程
}
}
return dp[m][n];
}
};
3. 珠宝的最高价值
LCR 166. 珠宝的最高价值
class Solution {
public:
int jewelleryValue(vector<vector<int>>& frame) {
int m = frame.size();
int n = frame[0].size();
// 创建dp表
vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
for(int i = 1; i < m+1; i++)
{
for(int j = 1; j < n+1; j++)
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + frame[i-1][j-1]; // 状态转移方程
}
return dp[m][n];
}
};
4. 下降路径最小和
931. 下降路径最小和


class Solution {
public:
int minFallingPathSum(vector<vector<int>>& matrix) {
int n = matrix.size();
// 创建dp表
vector<vector<int>> dp(n, vector<int>(n, 0));
// 初始化第一行的dp值
for(int i = 0; i < n; i++)
dp[0][i] = matrix[0][i];
for(int i = 1; i < n; i++)
{
for(int j = 0; j < n; j++)
{
if(j == 0)
dp[i][j] = min(dp[i-1][j], dp[i-1][j+1]) + matrix[i][j];
else if(j == n-1)
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + matrix[i][j];
else
dp[i][j] = min( min(dp[i-1][j-1], dp[i-1][j]), dp[i-1][j+1] ) + matrix[i][j]; // 状态转移方程
}
}
// 对比最后一行的所有dp值,最小的一个就是最终结果
int min = dp[n-1][0];
for(int i = 1; i < n; i++)
{
if(dp[n-1][i] < min)
min = dp[n-1][i];
}
return min;
}
};
5. 地下城游戏(与前4题的推导方法 不同)
174. 地下城游戏
class Solution {
public:
int calculateMinimumHP(vector<vector<int>>& dungeon) {
int m = dungeon.size();
int n = dungeon[0].size();
vector<vector<int>> dp(m+1, vector<int>(n+1, INT_MAX));
dp[m][n-1] = 1, dp[m-1][n] = 1;
for(int i = m-1; i >= 0 ; i--)
{
for(int j = n-1; j >= 0; j--)
{
if(dp[i][j+1] > dp[i+1][j])
dp[i][j] = max( dp[i+1][j] - dungeon[i][j], 1);
else
dp[i][j] = max( dp[i][j+1] - dungeon[i][j], 1);
}
}
return dp[0][0];
}
};
三、简单多状态 dp 问题
1. 按摩师
面试题 17.16. 按摩师
class Solution {
public:
int massage(vector<int>& nums) {
if(nums.size() == 0)
return 0;
int n = nums.size();
// dp表
vector<int> f(n, 0);
vector<int> g(n, 0);
f[0] = nums[0];
for(int i = 1; i < nums.size(); i++)
{
// 状态转移方程
f[i] = g[i-1] + nums[i];
g[i] = max(f[i-1], g[i-1]);
}
return max(f[n-1], g[n-1]);
}
};
2. 打家劫舍 II
213. 打家劫舍 II
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n == 1)
return nums[0];
vector<int> f(n, 0);
vector<int> g(n, 0);
// 1. 情况1
// 初始化
f[0] = nums[0];
g[1] = f[0];
// 实际上,0号房必选的情况下,无法选1号房,
// 将 f[1]的值初始化成 与 g[1]一样,是为了便于后续推导,
// 而且 由于f[1] 的值与 g[1]一样,后续无论选到谁 都是 g[1]的值,不影响后续结果
f[1] = g[1];
for(int i = 2; i < n-1; i++)
{
f[i] = g[i-1] + nums[i];
g[i] = max(f[i-1], g[i-1]);
}
g[n-1] = max(f[n-2], g[n-2]);
int max_money = g[n-1];
// 1. 情况2
// 初始化
g[0] = 0;
g[1] = g[0];
f[1] = g[0] + nums[1];
for(int i = 2; i < n; i++)
{
f[i] = g[i-1] + nums[i];
g[i] = max(f[i-1], g[i-1]);
}
if(max(f[n-1],g[n-1]) > max_money)
max_money = max(f[n-1],g[n-1]);
return max_money;
}
};
3. 删除并获得点数
740. 删除并获得点数
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<int> sums;
int left = 0, right = 0;
while(right < nums.size())
{
if(nums[right] != nums[left])
{
sums.push_back(nums[left] * (right - left));
if(nums[right] - nums[left] > 1)
sums.push_back(0);
left = right;
}
right++;
}
sums.push_back(nums[left] * (right - left));
// 进行动态规划
int n = sums.size();
vector<int> f(n, 0);
vector<int> g(n, 0);
f[0] = sums[0];
for(int i = 1; i < n; i++)
{
f[i] = g[i-1] + sums[i];
g[i] = max(f[i-1], g[i-1]);
}
return max(f[n-1], g[n-1]);
}
};
4. 粉刷房子
LCR 091. 粉刷房子
class Solution {
public:
int minCost(vector<vector<int>>& costs) {
int n = costs.size();
// 创建dp表
vector<int> f(n, 0);
vector<int> g(n, 0);
vector<int> h(n, 0);
// 初始化
f[0] = costs[0][0];
g[0] = costs[0][1];
h[0] = costs[0][2];
for(int i = 1; i < n; i++)
{
// 状态转移方程
f[i] = min(g[i-1], h[i-1]) + costs[i][0];
g[i] = min(f[i-1], h[i-1]) + costs[i][1];
h[i] = min(f[i-1], g[i-1]) + costs[i][2];
}
return min( min(f[n-1], g[n-1]), h[n-1] );
}
};
5. 买卖股票的最佳时机含冷冻期
309. 买卖股票的最佳时机含冷冻期
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if(n == 1)
return 0;
// 创建dp表
vector<int> f(n, 0);
vector<int> g(n, 0);
vector<int> h(n, 0);
vector<int> d(n, 0);
// 初始化
f[0] = 0, g[0] = -prices[0];
f[1] = f[0], g[1] = max(g[0], f[0] - prices[1]), h[1] = g[0] + prices[1];
if(n > 2)
{
f[2] = f[1], g[2] = max(g[1], f[1] - prices[2]), h[2] = g[1] + prices[2], d[2] = h[1];
}
for(int i = 3; i < n; i++)
{
// 状态转移方程
f[i] = max(f[i-1], d[i-1]);
g[i] = max( max(g[i-1], d[i-1] - prices[i]), f[i-1] - prices[i]);
h[i] = g[i-1] + prices[i];
d[i] = h[i-1];
}
return max( max(f[n-1], g[n-1]), max(h[n-1], d[n-1]) );
}
};
四、子数组、子串系列(数组中连续的一段)
1. 最大子数组和
53. 最大子数组和
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n, 0);
dp[0] = nums[0];
int max = dp[0];
for(int i = 1; i < n; i++)
{
if(dp[i-1] < 0)
dp[i] = nums[i];
else
dp[i] = dp[i-1] + nums[i];
if(dp[i] > max)
max = dp[i];
}
return max;
}
};
2. 环形子数组的最大和
918. 环形子数组的最大和
class Solution {
public:
int maxSubarraySumCircular(vector<int>& nums) {
int n = nums.size();
// dp表:以 i 位置为结尾的 最大和连续子数组(不考虑首尾相连)
vector<int> dp(n, 0);
// 初始化
dp[0] = nums[0];
// 找出 dp表 和 sp表中的最大值
int max = dp[0];
// 顺便计算数组总和
int sum = nums[0];
for(int i = 1; i < n; i++)
{
if(dp[i-1] < 0)
dp[i] = nums[i];
else
dp[i] = dp[i-1] + nums[i];
if(dp[i] > max)
max = dp[i];
sum += nums[i];
}
// fp表:以 i 位置为起点的 最小和连续子数组(不考虑首尾相连),用来推算sp表
vector<int> fp(n, 0);
// sp表:以 i 位置为结尾的 最大和连续子数组(只考虑 结果在数组首尾相连部分 的情况)
vector<int> sp(n, 0);
// 初始化
fp[n-1] = nums[n-1];
sp[n-1] = sum;
if(sp[n-1] > max)
max = sp[n-1];
for(int i = n-2; i >= 0; i--)
{
if(fp[i+1] < 0)
{
fp[i] = fp[i+1] + nums[i];
sp[i] = sum - fp[i+1];
}
else
{
fp[i] = nums[i];
sp[i] = sum;
}
if(sp[i] > max)
max = sp[i];
}
return max;
}
};
3. 乘积最大子数组
152. 乘积最大子数组
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
// 创建dp表
vector<int> d(n, 0);
vector<int> f(n, 0);
// 初始化
d[0] = f[0] = nums[0];
int max = d[0];
for(int i = 1; i < n; i++)
{
if(nums[i] > 0 && f[i-1] <= 0)
f[i] = nums[i] * f[i-1];
else if(nums[i] < 0 && d[i-1] > 0)
f[i] = nums[i] * d[i-1];
else
f[i] = nums[i];
if(nums[i] > 0 && d[i-1] > 0)
d[i] = nums[i] * d[i-1];
else if(nums[i] < 0 && f[i-1] <= 0)
d[i] = nums[i] * f[i-1];
else
d[i] = nums[i];
if(d[i] > max)
max = d[i];
}
return max;
}
};
4. 单词拆分
139. 单词拆分
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
int n = s.size();
// dp表
vector<bool> dp(n+1, false);
// 初始化
dp[0] = true;
for(int i = 0; i < n; i++)
{
bool flag = false;
for(auto word : wordDict)
{
int sz = word.size();
int left = i - sz + 1;
if(left >= 0 && dp[left] == true)
{
string str = s.substr(left, sz);
int n = strcmp(word.c_str(), str.c_str());
if(n == 0)
{
flag = true;
break;
}
}
}
dp[i+1] = flag;
}
return dp[n];
}
};
5. 环绕字符串中唯一的子字符串
467. 环绕字符串中唯一的子字符串
class Solution {
public:
int findSubstringInWraproundString(string s) {
int n = s.size();
// 创建 dp表
vector<int> dp(n,0);
// 初始化
dp[0] = 1;
// arr数组:用来去重
int arr[26] = {0};
arr[s[0] - 'a'] = 1;
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] + 1;
else
dp[i] = 1;
if(dp[i] > arr[s[i] - 'a'])
arr[s[i] - 'a'] = dp[i];
}
int sum = 0;
for(int i = 0; i < 26; i++)
sum += arr[i];
return sum;
}
};
五、子序列问题(数组中不连续的一段)
1. 最长递增子序列
300. 最长递增子序列
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
// 创建dp表
vector<int> dp(n, 0);
// 初始化
dp[0] = 1;
int length = 1;
for(int i = 1; i < n; i++)
{
int len = 1;
for(int j = 0; j < i; j++)
{
if(nums[j] < nums[i])
{
if(dp[j] + 1 > len)
len = dp[j] + 1;
}
}
dp[i] = len;
if(dp[i] > length)
length = dp[i];
}
return length;
}
};
2. 摆动序列
376. 摆动序列
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n = nums.size();
// 创建dp表
vector<int> dp(n, 0);
vector<int> sp(n, 0);
// 初始化
dp[n-1] = sp[n-1] = 1;
int max_len = 1;
for(int i = n-2; i >= 0; i--)
{
int dp_num = 1;
int sp_num = 1;
for(int j = i+1; j < n; j++)
{
if(nums[i] > nums[j])
{
if(sp[j] + 1 > dp_num)
dp_num = sp[j] + 1;
}
if(nums[i] < nums[j])
{
if(dp[j] + 1 > sp_num)
sp_num = dp[j] + 1;
}
}
dp[i] = dp_num;
if(dp[i] > max_len)
max_len = dp[i];
sp[i] = sp_num;
if(sp[i] > max_len)
max_len = sp[i];
}
return max_len;
}
};
3. 最长递增子序列的个数
673. 最长递增子序列的个数
class Solution {
public:
int findNumberOfLIS(vector<int>& nums) {
int n = nums.size();
vector< pair<int, int> > dp(n, {0, 0});
dp[0] = {1, 1};
int arr[2] = {1, 1};
for(int i = 1; i < n; i++)
{
int len = 1;
int count = 1;
for(int j = 0; j < i; j++)
{
if(nums[i] > nums[j])
{
if(dp[j].first + 1 > len)
{
len = dp[j].first + 1;
count = dp[j].second;
}
else if(dp[j].first + 1 == len)
count += dp[j].second;;
}
}
dp[i] = {len, count};
if(len > arr[0])
{
arr[0] = len;
arr[1] = count;
}
else if(len == arr[0])
arr[1] += count;
}
return arr[1];
}
};
4. 最长定差子序列
1218. 最长定差子序列
class Solution {
public:
int longestSubsequence(vector<int>& arr, int difference) {
int n = arr.size();
// 创建 并 初始化dp表
vector<int> dp(n, 1);
// 创建hash表
unordered_map<int, int> hash;
// 统计dp表中的最大值
int max = 1;
for(int i = 1; i < n; i++)
{
hash[ arr[i-1] ] = i-1;
auto iter = hash.find(arr[i] - difference);
if(iter != hash.end())
dp[i] = dp[ iter->second ] + 1;
if(dp[i] > max)
max = dp[i];
}
return max;
}
};
5. 最长等差数列(二维bp表)
1027. 最长等差数列
class Solution {
public:
int longestArithSeqLength(vector<int>& nums) {
int n = nums.size();
// 创建 并 初始化dp表
vector< vector<int> > dp(n, vector<int>(n, 2));
// 创建hash表
unordered_map<int, int> hash;
// 统计dp表中的最大值
int max = 2;
for(int i = 1; i < n; i++)
{
hash[ nums[i-1] ] = i-1;
for(int j = i + 1; j < n; j++)
{
int x = 2 * nums[i] - nums[j];
auto it = hash.find(x);
if(it != hash.end())
{
dp[i][j] = dp[it->second][i] + 1;
}
if(dp[i][j] > max)
max = dp[i][j];
}
}
return max;
}
};
6. 最长的斐波那契子序列的长度
873. 最长的斐波那契子序列的长度
class Solution {
public:
int lenLongestFibSubseq(vector<int>& arr) {
int n = arr.size();
// 创建 并 初始化dp表
vector< vector<int> > dp(n, vector<int>(n, 2));
// 创建hash表
unordered_map<int, int> hash;
// 统计dp表中的最大值
int max = 2;
for(int i = 1; i < n; i++)
{
hash[ arr[i-1] ] = i-1;
for(int j = i + 1; j < n; j++)
{
int x = arr[j] - arr[i];
auto it = hash.find(x);
if(it != hash.end())
{
dp[i][j] = dp[it->second][i] + 1;
}
if(dp[i][j] > max)
max = dp[i][j];
}
}
if(max < 3)
return 0;
return max;
}
};
六、回文子串问题
1. 回文子串(模板题)
647. 回文子串
class Solution {
public:
int countSubstrings(string s) {
int n = s.size();
// 创建 dp表
vector<vector<bool>> dp(n, vector<bool>(n, false));
// 计算 dp表中 true 的总数
int count = 0;
for(int i = n-1; i >= 0; i--)
{
for(int j = i; j < n; j++)
{
if(s[i] == s[j])
{
if(j - i <= 1)
{
dp[i][j] = true;
count++;
}
else
{
dp[i][j] = dp[i+1][j-1];
if(dp[i][j])
count++;
}
}
}
}
return count;
}
};
2. 分割回文串 IV(hard)
1745. 分割回文串 IV
class Solution {
public:
bool checkPartitioning(string s) {
int n = s.size();
// 1. ⽤ dp 把所有的⼦串是否是回⽂预处理⼀下
vector<vector<bool>> dp(n, vector<bool>(n, false));
for(int i = n-1; i >= 0; i--)
{
for(int j = i; j < n; j++)
{
if(s[i] == s[j])
{
if(j - i <= 1)
{
dp[i][j] = true;
}
else
{
dp[i][j] = dp[i+1][j-1];
}
}
}
}
// 2. 枚举所有的第⼆个字符串的起始位置以及结束位置
for(int i = 1; i < n-1; i++)
{
for(int j = i; j < n-1; j++)
{
if(dp[0][i-1] == true && dp[i][j] == true && dp[j+1][n-1] == true)
return true;
}
}
return false;
}
};
3. 分割回文串 II(hard)
132. 分割回文串 II
class Solution {
public:
int minCut(string s) {
int n = s.size();
// 1. ⽤ dp 把所有的⼦串是否是回⽂预处理⼀下
vector<vector<bool>> dp(n, vector<bool>(n, false));
for(int i = n-1; i >= 0; i--)
{
for(int j = i; j < n; j++)
{
if(s[i] == s[j])
{
if(j - i <= 1)
{
dp[i][j] = true;
}
else
{
dp[i][j] = dp[i+1][j-1];
}
}
}
}
// 2. 用 fp 推导最小分割次数
vector<int> fp(n, 0);
for(int j = 1; j < n; j++)
{
int min = INT_MAX;
// 循环遍历一遍 i 的取值
for(int i = 0; i <= j; i++)
{
if(dp[i][j] == true)
{
if(i == 0)
{
min = 0;
break;
}
else
{
if(fp[i-1] + 1 < min)
min = fp[i-1] + 1;
}
}
}
fp[j] = min;
}
return fp[n-1];
}
};
4. 最长回文子序列
516. 最长回文子序列
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.size();
vector< vector<int> > dp(n, vector<int>(n, 1));
for(int i = n-1; i >= 0; i--)
{
for(int j = i; j < n; j++)
{
if(s[i] == s[j])
{
if(j - i < 2)
dp[i][j] = j-i+1;
else
dp[i][j] = dp[i+1][j-1] + 2;
}
else
{
dp[i][j] = max(dp[i][j-1], dp[i+1][j]);
}
}
}
return dp[0][n-1];
}
};
七、两个数组的 dp(含字符串数组)
1. 最长公共子序列(模板题)
1143. 最长公共子序列
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int n = text1.size(), m = text2.size();
// 创建并初始化 dp表
vector< vector<int> > dp(n+1, vector<int>(m+1, 0));
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; 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[n][m];
}
};
2. 不同的子序列(hard)
115. 不同的子序列
class Solution {
public:
int numDistinct(string s, string t) {
int n = s.size(), m = t.size();
// 创建dp表
vector< vector<unsigned long long> > dp(n+1, vector<unsigned long long>(m+1, 0));
// 初始化dp表
for(int i = 0; i <= n; i++)
dp[i][0] = 1;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
if(s[i-1] == t[j-1])
dp[i][j] = dp[i-1][j] + dp[i-1][j-1];
else
dp[i][j] = dp[i-1][j];
}
}
return dp[n][m];
}
};
3. 交错字符串(子序列)
97. 交错字符串
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int n = s1.size();
int m = s2.size();
int w = s3.size();
if(n+m != w)
{
return false;
}
s1.insert(0, "_");
s2.insert(0, "_");
s3.insert(0, "_");
// 创建 dp表
vector< vector<bool> > dp(n+1, vector<bool>(m+1, false));
// 初始化 dp表
dp[0][0] = true;
for(int i = 1; i <= n; i++)
{
if(s1[i] == s3[i] && dp[i-1][0] == true)
dp[i][0] = true;
}
for(int j = 1; j <= m; j++)
{
if(s2[j] == s3[j] && dp[0][j-1] == true)
dp[0][j] = true;
}
// 状态转移推导
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
if( (s1[i] == s3[i+j] && dp[i-1][j] == true) ||
(s2[j] == s3[i+j] && dp[i][j-1] == true) )
{
dp[i][j] = true;
}
}
}
return dp[n][m];
}
};
4. 最长重复子数组(子数组连续,模板题)
718. 最长重复子数组
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
// 创建并初始化 dp表
vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
int max = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
if(nums1[i-1] == nums2[j-1])
{
dp[i][j] = dp[i-1][j-1] + 1;
if(dp[i][j] > max)
max = dp[i][j];
}
}
}
return max;
}
};
八、背包问题概述
1. 01背包(模板题)
DP41 【模板】01背包
- 方案二:要求最终恰好装满背包,求能获得的最大总价值。若不存在使背包恰好装满的装法,则答案记为 0
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, V;
cin >> n >> V;
vector<int> v(n+1, 0);
vector<int> w(n+1, 0);
for(int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i];
}
// 方案一:不要求装满背包,求能获得的最大总价值
// 创建并初始化dp表
vector<vector<int>> dp(n+1, vector<int>(V+1, 0));
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= V; j++)
{
if(j - v[i] >= 0)
dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]);
else
dp[i][j] = dp[i-1][j];
}
}
printf("%d\n", dp[n][V]);
// 方案二:要求最终恰好装满背包,求能获得的最大总价值。若不存在使背包恰好装满的装法,则答案记为 0
// 初始化 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++)
{
if(j - v[i] >= 0)
{
if(dp[i-1][j - v[i]] == -1)
dp[i][j] = max(dp[i-1][j], -1);
else
dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]);
}
else
dp[i][j] = dp[i-1][j];
}
}
if(dp[n][V] == -1)
printf("%d\n", 0);
else
printf("%d\n", dp[n][V]);
return 0;
}
2. 分割等和子集
416. 分割等和子集
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for(auto it : nums)
sum += it;
if(sum % 2 == 1)
return false;
int target = sum / 2;
int n = nums.size();
nums.insert(nums.begin(), -1);
// 创建并初始化 dp表
vector<vector<bool>> dp(n+1, vector<bool>(target+1, false));
for(int i = 0; i <= n; i++)
dp[i][0] = true;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= target; j++)
{
if(j - nums[i] >= 0)
{
if(dp[i-1][j] || dp[i-1][j-nums[i]])
dp[i][j] = true;
}
else
dp[i][j] = dp[i-1][j];
}
}
return dp[n][target];
}
};
3. 目标和(特殊)
494. 目标和
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = 0;
for(auto it : nums)
sum += it;
if( sum + target < 0 || (sum+target)%2 == 1 )
return 0;
int sum_A = (sum+target) / 2;
if(sum_A > sum)
return 0;
int n = nums.size();
nums.insert(nums.begin(), -1);
// 创建并初始化 dp表
vector<vector<int>> dp(n+1, vector<int>(sum_A+1, 0));
dp[0][0] = 1;
for(int i = 1; i <= n; i++)
{
// 从 dp[1][0]开始往后推导
for(int j = 0; j <= sum_A; j++)
{
if(j-nums[i] >= 0)
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]];
else
dp[i][j] = dp[i-1][j];
}
}
return dp[n][sum_A];
}
};
4. 完全背包(模板题)
DP42 【模板】完全背包
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, V;
cin >> n >> V;
vector<int> v(n+1, 0);
vector<int> w(n+1, 0);
for(int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i];
}
// 求这个背包至多能装多大价值的物品
// 创建并初始化 dp表
vector<vector<int>> dp(n+1, vector<int>(V+1, 0));
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= V; j++)
{
if(j - v[i] >= 0)
dp[i][j] = max( dp[i-1][j], dp[i][j-v[i]] + w[i]);
else
dp[i][j] = dp[i-1][j];
}
}
printf("%d\n", dp[n][V]);
// 若背包恰好装满,求至多能装多大价值的物品
// 初始化 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++)
{
if(j - v[i] >= 0 && dp[i][j-v[i]] != -1)
dp[i][j] = max( dp[i-1][j], dp[i][j-v[i]] + w[i]);
else
dp[i][j] = dp[i-1][j];
}
}
if(dp[n][V] == -1)
printf("%d\n", 0);
else
printf("%d\n", dp[n][V]);
return 0;
}
5. 完全平方数
279. 完全平方数
class Solution {
public:
int numSquares(int n) {
vector<int> nums(1, -1);
int squ = 1;
while(squ * squ <= n)
{
nums.push_back(squ * squ);
squ++;
}
int count = nums.size()-1;
// 创建并初始化 dp表
vector<vector<int>> dp(count+1, vector<int>(n+1, 0));
for(int j = 1; j <= n; j++)
dp[0][j] = INT_MAX;
for(int i = 1; i <= count; i++)
{
for(int j = 1; j <= n; j++)
{
if(j-nums[i] >= 0 && dp[i][j-nums[i]] != INT_MAX)
dp[i][j] = min( dp[i-1][j], dp[i][j-nums[i]] + 1 );
else
dp[i][j] = dp[i-1][j];
}
}
return dp[count][n];
}
};
6. 一和零(二维限制条件的 01背包问题)
474. 一和零
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
int len = strs.size();
strs.insert(strs.begin(), "");
// 统计每个字符串中 字符0 和 字符1的个数
vector<int> x(len+1, 0);
vector<int> y(len+1, 0);
for(int i = 1; i <= len; i++)
{
int x_0 = 0;
int y_1 = 0;
for(auto it : strs[i])
{
if(it == '0')
x_0++;
else
y_1++;
}
x[i] = x_0;
y[i] = y_1;
}
// 创建并初始化 dp表
vector<vector<vector<int>>> dp(len+1, vector<vector<int>>(m+1, vector<int>(n+1, 0)));
for(int i = 1; i <= len; i++)
{
for(int j = 0; j <= m; j++)
{
for(int k = 0; k <= n; k++)
{
if(j - x[i] >= 0 && k - y[i] >= 0)
dp[i][j][k] = max( dp[i-1][j][k], dp[i-1][j-x[i]][k-y[i]] + 1);
else
dp[i][j][k] = dp[i-1][j][k];
}
}
}
return dp[len][m][n];
}
};
7. 组合总和 Ⅳ(似包非包)
377. 组合总和 Ⅳ
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
// 创建并初始化 dp表
vector<unsigned long long> dp(target+1, 0);
dp[0] = 1;
for(int i = 1; i <= target; i++)
{
for(auto it : nums)
{
if(i - it >= 0)
dp[i] += dp[i-it];
}
}
return dp[target];
}
};