代码随想录算法训练营第四十五天|Day45 动态规划
115.不同的子序列
https://programmercarl.com/0115.%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97.html
思路
#define MOD 1000000007
int numDistinct(char* s, char* t) {
int m = strlen(s);
int n = strlen(t);
int dp[m + 1][n + 1];
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for (int i = 0; i <= m; i++) {
dp[i][0] = 1;
}
for (int j = 1; j <= n; j++) {
dp[0][j] = 0;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; 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[m][n];
}
学习反思
动态规划解决了一个字符串问题。函数numDistinct
计算字符串t
在字符串s
中出现的次数。代码中使用了一个二维数组dp
来存储状态。dp[i][j]
表示s
的前i
个字符中包含字符串t
的前j
个字符的子序列数。首先,初始化dp[0][0]
为1表示空字符串是空字符串的子序列。然后,初始化dp[i][0]
为1表示任意非空字符串是空字符串的子序列。初始化dp[0][j]
为0表示任意字符串不能包含非空字符串。接下来,使用两层循环遍历数组s
和数组t
的每个字符。如果s[i-1]
与t[j-1]
相等,则有两种情况:一种是s
的前i-1
个字符中包含t
的前j-1
个字符的子序列数,另一种是s
的前i-1
个字符中包含t
的前j
个字符的子序列数。所以,dp[i][j]
等于这两种情况的和。如果s[i-1]
与t[j-1]
不相等,则dp[i][j]
等于s
的前i-1
个字符中包含t
的前j
个字符的子序列数。最后,返回dp[m][n]
,即s
包含t
的子序列数。这段代码的时间复杂度是O(m*n),其中m和n分别是字符串s
和t
的长度。
583. 两个字符串的删除操作
https://programmercarl.com/0583.%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C.html
思路
int minDistance(char* word1, char* word2) {
int m = strlen(word1);
int n = strlen(word2);
int **dp = (int **)malloc((m + 1) * sizeof(int *));
for (int i = 0; i <= m; i++) {
dp[i] = (int *)malloc((n + 1) * sizeof(int));
}
for (int i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= n; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1[i - 1] == word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = (dp[i - 1][j] < dp[i][j - 1]) ? dp[i - 1][j] + 1 : dp[i][j - 1] + 1;
}
}
}
int result = dp[m][n];
for (int i = 0; i <= m; i++) {
free(dp[i]);
}
free(dp);
return result;
}
学习反思
动态规划解决了一个字符串问题。函数minDistance
计算将字符串word1
转换成字符串word2
所需的最小操作次数。代码中使用了一个二维数组dp
来存储状态。dp[i][j]
表示将word1
的前i
个字符转换成word2
的前j
个字符所需的最小操作次数。首先,动态申请二维数组dp
的内存空间,并初始化数组。初始化dp[i][0]
为i
,表示将word1
的前i
个字符转换成空字符串所需的最小操作次数。初始化dp[0][j]
为j
,表示将空字符串转换成word2
的前j
个字符所需的最小操作次数。接下来,使用两层循环遍历字符串word1
和字符串word2
的每个字符。如果word1[i-1]
与word2[j-1]
相等,则不需要进行任何操作,所以dp[i][j]
等于dp[i-1][j-1]
。如果word1[i-1]
与word2[j-1]
不相等,则可以选择删除word1[i-1]
或者插入word2[j-1]
,取两种操作中的较小值,并加上一次操作的代价。最后,返回dp[m][n]
,即将word1
转换成word2
所需的最小操作次数。由于申请了动态内存空间,需要在函数结束后释放内存,以避免内存泄漏。这段代码的时间复杂度是O(m*n),其中m和n分别是字符串word1
和word2
的长度。
72. 编辑距离
https://programmercarl.com/0072.%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB.html
思路
int minDistance(char* word1, char* word2) {
int m = strlen(word1);
int n = strlen(word2);
int **dp = (int **)malloc((m + 1) * sizeof(int *));
for (int i = 0; i <= m; i++) {
dp[i] = (int *)malloc((n + 1) * sizeof(int));
}
for (int i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= n; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1[i - 1] == word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = fmin(fmin(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + 1);
}
}
}
int result = dp[m][n];
for (int i = 0; i <= m; i++) {
free(dp[i]);
}
free(dp);
return result;
}
学习反思
在计算dp[i][j]
的时候,使用了fmin
函数来取三者中的最小值,即dp[i - 1][j] + 1
、dp[i][j - 1] + 1
和dp[i - 1][j - 1] + 1
中的最小值。这样可以简化代码并提高效率。其他部分保持不变,申请动态内存空间、初始化数组、更新dp[i][j]
的值和释放内存空间的操作都没有改变。这段代码的时间复杂度仍然是O(m*n)。通过使用fmin
函数,可以更清晰地表示出取三者中的最小值的操作。
总结
加油!!!