本文不再从递归入手,而是直接从动态规划的定义入手,来解决更多二维动态规划问题。其中包含一些比较巧妙的尝试思路。
题目一
测试链接:https://leetcode.cn/problems/distinct-subsequences/
分析:dp数组的含义是字符串s前i个字符中出现字符串t前j个字符的次数。代码如下。
class Solution {
public:int dp[1001][1001];int MOD = 1000000007;int numDistinct(string s, string t) {int s_length = s.size();int t_length = t.size();dp[0][0] = 1;for(int i = 1;i <= t_length;++i){dp[0][i] = 0;}for(int i = 1;i <= s_length;++i){dp[i][0] = 1;}for(int i = 1;i <= s_length;++i){for(int j = 1;j <= t_length;++j){if(s[i-1] == t[j-1]){dp[i][j] = (dp[i-1][j-1] + dp[i-1][j]) % MOD;}else{dp[i][j] = dp[i-1][j];}}}return dp[s_length][t_length];}
};
从代码中可以看出做空间压缩很容易。代码如下。
class Solution {
public:int dp[1001];int leftup1, leftup2;int MOD = 1000000007;int numDistinct(string s, string t) {int s_length = s.size();int t_length = t.size();for(int i = 1;i <= t_length;++i){dp[i] = 0;}for(int i = 1;i <= s_length;++i){leftup1 = 1;for(int j = 1;j <= t_length;++j){leftup2 = dp[j];if(s[i-1] == t[j-1]){dp[j] = (leftup1 + dp[j]) % MOD;}leftup1 = leftup2;}}return dp[t_length];}
};
其中,辅助变量leftup含义和之前的文章一样。
题目二
测试链接:https://leetcode.cn/problems/edit-distance/
分析:dp数组含义为word1前i字符转换成word2前j个字符所使用的最少操作数。那么就有两种可能,一个是word1的最后一个字符参与变换,一个是word1的最后一个字符不参与变换。对于参与变换来说,一是这最后一个字符变为了word2的最后一个字符,二是最后一个字符变为了word2的倒数第二个字符,这word2的最后一个字符通过插入得到。对于不参与变换来说,次数为word1的前i-1个字符变换为word2的前j个字符的次数加上减去word1最后一个字符。这三种情况取最小值。代码如下。
class Solution {
public:int dp[501][501];int minDistance(string word1, string word2) {int word1_length = word1.size();int word2_length = word2.size();dp[0][0] = 0;for(int i = 1;i <= word2_length;++i){dp[0][i] = i;}for(int i = 1;i <= word1_length;++i){dp[i][0] = i;}for(int i = 1;i <= word1_length;++i){for(int j = 1;j <= word2_length;++j){if(word1[i-1] == word2[j-1]){dp[i][j] = dp[i-1][j-1];}else{dp[i][j] = dp[i-1][j-1]+1;}dp[i][j] = dp[i][j] < dp[i][j-1]+1 ? dp[i][j] : dp[i][j-1]+1;dp[i][j] = dp[i][j] < dp[i-1][j]+1 ? dp[i][j] : dp[i-1][j]+1;}}return dp[word1_length][word2_length];}
};
从代码中可以看出做空间压缩很容易。代码如下。
class Solution {
public:int dp[501];int leftup1, leftup2;int minDistance(string word1, string word2) {int word1_length = word1.size();int word2_length = word2.size();dp[0] = 0;for(int i = 1;i <= word2_length;++i){dp[i] = i;}for(int i = 1;i <= word1_length;++i){leftup1 = i-1;dp[0] = i;for(int j = 1;j <= word2_length;++j){leftup2 = dp[j];if(word1[i-1] == word2[j-1]){dp[j] = leftup1;}else{dp[j] = leftup1+1;}dp[j] = dp[j] < dp[j-1]+1 ? dp[j] : dp[j-1]+1;dp[j] = dp[j] < leftup2+1 ? dp[j] : leftup2+1;leftup1 = leftup2;}}return dp[word2_length];}
};
题目三
测试链接:https://leetcode.cn/problems/interleaving-string/
分析:dp数组的含义为s1的前i个字符和s2的前j个字符能否交错组成s3的前i+j个字符。代码如下。
class Solution {
public:bool dp[101][101] = {false};bool isInterleave(string s1, string s2, string s3) {int length_s1 = s1.size();int length_s2 = s2.size();int length_s3 = s3.size();if(length_s3 != length_s1 + length_s2){return false;}dp[0][0] = true;for(int i = 1;i <= length_s2;++i){dp[0][i] = dp[0][i-1] && s2[i-1] == s3[i-1];}for(int i = 1;i <= length_s1;++i){dp[i][0] = dp[i-1][0] && s1[i-1] == s3[i-1];}for(int i = 1;i <= length_s1;++i){for(int j = 1;j <= length_s2;++j){if(s3[i+j-1] == s1[i-1]){dp[i][j] |= dp[i-1][j];}if(s3[i+j-1] == s2[j-1]){dp[i][j] |= dp[i][j-1];}}}return dp[length_s1][length_s2];}
};
下面是做空间压缩的解法。代码如下。
class Solution {
public:bool dp[101] = {false};bool isInterleave(string s1, string s2, string s3) {int length_s1 = s1.size();int length_s2 = s2.size();int length_s3 = s3.size();if(length_s3 != length_s1 + length_s2){return false;}dp[0] = true;for(int i = 1;i <= length_s2;++i){dp[i] = dp[i-1] && s2[i-1] == s3[i-1];}bool temp;for(int i = 1;i <= length_s1;++i){dp[0] = dp[0] && s1[i-1] == s3[i-1];for(int j = 1;j <= length_s2;++j){temp = dp[j];dp[j] = false;if(s3[i+j-1] == s1[i-1]){dp[j] |= temp;}if(s3[i+j-1] == s2[j-1]){dp[j] |= dp[j-1];}}}return dp[length_s2];}
};
题目四
有效涂色问题
给定n、m两个参数
一共有n个格子,每个格子可以涂上一种颜色,颜色在m种里选
当涂满n个格子,并且m种颜色都使用了,叫一种有效方法
求一共有多少种有效的涂色方法
1 <= n, m <= 5000
结果比较大请 % 1000000007 之后返回
分析:dp数组的含义是i个格子j种颜色有多少种有效的涂色方法。代码如下。
#include <iostream>
#define MOD 1000000007
using namespace std;
int n, m;
int dp[5001][5001] = {0};
int main(void){scanf("%d%d", &n, &m);for(int i = 0;i <= n && i <= m;++i){dp[i][i] = 1;}for(int i = 1;i <= n;++i){dp[i][0] = 1;dp[i][1] = 1;}for(int i = 1;i <= n;++i){for(int j = 1;j <= m && i >= j;++j){dp[i][j] = (dp[i-1][j] * j + dp[i-1][j-1] * (m - j + 1)) % MOD;}}printf("%d", dp[n][m]);
}
题目五
删除至少几个字符可以变成另一个字符串的子串
给定两个字符串s1和s2
返回s1至少删除多少字符可以成为s2的子串
分析:这道题换个说法就非常简单了。我们只需要求出s1和s2的最长公共子序列,而s1至少删除多少字符可以成为s2的子串,就是s1的长度减去最长公共子序列的长度。而求最长公共子序列前面的文章中已经求过,这里不在给出代码。