文章目录
所有的LeetCode题解索引,可以看这篇文章------【算法和数据结构】LeetCode题解。
一、718、最长重复子数组

思路分析:
- 第一步,动态数组的含义。 d p i j dpij dpij代表以下标 i − 1 i - 1 i−1为结尾的nums1,和以下标 j − 1 j - 1 j−1为结尾的nums2,最长重复子数组长度为 d p i j dpij dpij。
- 第二步,递推公式。根据 d p i j dpij dpij的定义, d p i j dpij dpij的状态只能由 d p i − 1 j − 1 dpi - 1j - 1 dpi−1j−1推导出来。
cpp
if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
- 第三步,元素初始化。dp数组中的所有元素都初始化为0。
- 第四步,递归顺序。一共有两层循环,先遍历nums1或者先遍历nums2都可以。
- 第五步,打印结果。题目要求长度最长的子数组的长度。所以在遍历的时候顺便把 d p i j dpij dpij的最大值记录下来。
程序如下:
cpp
// 718、最长重复子数组
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int result = 0;
for (int i = 1; i <= nums1.size(); i++) {
for (int j = 1; j <= nums2.size(); j++) {
if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
if (dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
};
复杂度分析:
- 时间复杂度: O ( n ∗ m ) O(n*m) O(n∗m), n n n和 m m m分别是两个数组的长度。
- 空间复杂度: O ( n ∗ m ) O(n*m) O(n∗m)。
二、1143、最长公共子序列

思路分析:
- 第一步,动态数组的含义。 d p i j dpij dpij代表以下标 i − 1 i - 1 i−1为结尾的text1,和以下标 j − 1 j - 1 j−1为结尾的text2,最长公共子序列长度为 d p i j dpij dpij。
- 第二步,递推公式。 d p i j dpij dpij可以由两种情况推导出来:
- t e x t 1 i − 1 text1i - 1 text1i−1与 t e x t 2 j − 1 text2j - 1 text2j−1相同:那么找到一个公共元素, d p i j = d p i − 1 j − 1 + 1 dpij = dpi - 1j - 1 + 1 dpij=dpi−1j−1+1。
- t e x t 1 i − 1 text1i - 1 text1i−1 与 t e x t 2 j − 1 text2j - 1 text2j−1不相同:那么 t e x t 1 0 , i − 2 text10, i - 2 text10,i−2与 t e x t 2 0 , j − 1 text20, j - 1 text20,j−1的最长公共子序列和 t e x t 1 0 , i − 1 text10, i - 1 text10,i−1与 t e x t 2 0 , j − 2 text20, j - 2 text20,j−2的最长公共子序列,取最大的。
cpp
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]);
- 第三步,元素初始化。dp数组中的所有元素都初始化为0。
- 第四步,递归顺序。一共有两层循环,从前往后进行遍历。
- 第五步,打印结果。题目要求最长公共子序列的长度。所以在遍历的时候顺便把 d p i j dpij dpij的最大值记录下来。
程序如下:
cpp
// 1143、最长公共子序列
class Solution2 {
public:
int longestCommonSubsequence(string text1, string text2) {
vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
int result = 0;
for (int i = 1; i <= text1.size(); i++) {
for (int j = 1; j <= text2.size(); 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]);
if(dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
};
复杂度分析:
- 时间复杂度: O ( n ∗ m ) O(n*m) O(n∗m), n n n和 m m m分别是两个序列的长度。
- 空间复杂度: O ( n ∗ m ) O(n*m) O(n∗m)。
三、1035、不相交的线


思路分析:本题要求绘制的最大连线数,实际上就是求两个字符串的最长公共子序列的长度,即1143、最长公共子序列这道题。我们将字符串改成数组,代码完全一样,直接copy过来。
程序如下:
cpp
// 1035、不相交的线
class Solution3 {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int result = 0;
for (int i = 1; i <= nums1.size(); i++) {
for (int j = 1; j <= nums2.size(); j++) {
if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
if (dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
};
复杂度分析:
- 时间复杂度: O ( n ∗ m ) O(n*m) O(n∗m), n n n和 m m m分别是两个数组的长度。
- 空间复杂度: O ( n ∗ m ) O(n*m) O(n∗m)。
四、392、判断子序列

思路分析:本题的思路和1143、最长公共子序列的分析思路差不多,主要区别在于本题判断的是" 最长公共子序列是不是另一个字符串的子串"。那么我们找到二者的最长公共子串,判断其长度是否等于s的长度即可。
- 第一步,动态数组的含义。 d p i j dpij dpij代表以下标 i − 1 i - 1 i−1为结尾的s,和以下标 j − 1 j - 1 j−1为结尾的t,最长公共子序列长度为 d p i j dpij dpij。
- 第二步,递推公式。 d p i j dpij dpij可以由两种情况推导出来:
- s i − 1 si - 1 si−1与 t j − 1 tj - 1 tj−1相同:那么找到一个公共元素, d p i j = d p i − 1 j − 1 + 1 dpij = dpi - 1j - 1 + 1 dpij=dpi−1j−1+1。
- s i − 1 si - 1 si−1 与 t j − 1 tj - 1 tj−1不相同:那么 d p i j dpij dpij等于 s 0 , i − 1 s0, i - 1 s0,i−1与 t 0 , j − 2 t0, j - 2 t0,j−2的最长公共子序列。
cpp
if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = dp[i][j - 1]; // 与1143不同的地方
- 第三步,元素初始化。dp数组中的所有元素都初始化为0。
- 第四步,递归顺序。一共有两层循环,从前往后进行遍历。
- 第五步,打印结果。题目要求最长公共子序列的长度。所以在遍历的时候顺便把 d p i j dpij dpij的最大值记录下来,在用三目运算符返回。
cpp
return result == s.size() ? true : false; // 与1143不同的地方
程序如下:
cpp
// 392、判断子序列-动态规划
class Solution4 {
public:
bool isSubsequence(string s, string t) {
vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
int result = 0;
for (int i = 1; i <= s.size(); i++) {
for (int j = 1; j <= t.size(); j++) {
if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = dp[i][j - 1]; // 与1143不同的地方
if (dp[i][j] > result) result = dp[i][j];
}
}
return result == s.size() ? true : false; // 与1143不同的地方
}
};
复杂度分析:
- 时间复杂度: O ( n ∗ m ) O(n*m) O(n∗m), n n n和 m m m分别是两个字符串的长度。
- 空间复杂度: O ( n ∗ m ) O(n*m) O(n∗m)。
五、115、不同的子序列

思路分析:本题的思路和1143、最长公共子序列的分析思路差不多。本题统计字符串t在字符串s中出现的次数,我们可以理解为删除掉字符串s中的部分字符使得字符串s和字符串t相同的方法数量。
-
第一步,动态数组的含义。 d p i j dpij dpij代表以下标 j − 1 j - 1 j−1为结尾的t在以下标 i − 1 i - 1 i−1为结尾的s中出现的次数为 d p i j dpij dpij,即 t 0 , j − 1 t0, j-1 t0,j−1在 s 0 , i − 1 s0, i-1 s0,i−1中出现的次数。
-
第二步,递推公式。 d p i j dpij dpij可以由两种情况推导出来:
- s i − 1 si - 1 si−1与 t j − 1 tj - 1 tj−1相同:此时的 d p i j dpij dpij由两部分组成。一部分是用 s i − 1 si-1 si−1来匹配:相当于在 s 0 , i − 2 s0, i-2 s0,i−2中寻找 t 0 , j − 2 t0, j-2 t0,j−2的个数(剩下一个字符 s i − 1 si - 1 si−1与 t j − 1 tj - 1 tj−1已经匹配了),即 d p i − 1 j − 1 dpi-1j-1 dpi−1j−1;另一部分是不用 s i − 1 si-1 si−1来匹配,相当于在 s 0 , i − 2 s0, i-2 s0,i−2中寻找 t 0 , j − 1 t0, j-1 t0,j−1的个数,即 d p i − 1 j dpi-1j dpi−1j。
- s i − 1 si - 1 si−1 与 t j − 1 tj - 1 tj−1不相同:那么 s 0 , i − 2 s0, i - 2 s0,i−2中, t 0 , j − 1 t0, j - 1 t0,j−1的数量和 s 0 , i − 1 s0, i - 1 s0,i−1中, t 0 , j − 1 t0, j - 1 t0,j−1的数量相同。 d p i j = d p i − 1 j dpij = dpi-1j dpij=dpi−1j
cpp
if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
else dp[i][j] = dp[i - 1][j];
例子:s="bageg",t="bag"。那么用s4="g"组成bag的方法数量,相当于在s0, 3="bage"中寻找中t0, 1="ba"的个数,只有s0s1s4这一种。而不用s4="g"组成bag的方法数量,相当于在s0,3 ="bage"中,寻找t0,2="bag"的个数,即dp4, 3,只有s0s1s2这一种。(说明:dp4,2=1代表在s0,3 ="bage"中,t0,1="ba"的个数为1。)

- 第三步,元素初始化。 d p i 0 dpi0 dpi0(第一列)表示字符串 s 0 , i − 1 s0, i-1 s0,i−1中可以随便删除元素,出现空字符串的个数。 d p 0 j dp0j dp0j(第一行)表示空字符串 s s s,出现字符串 t 0 , j − 1 t0, j-1 t0,j−1的个数。其中,空字符串s中空字符串t的个数为1。那么 d p 0 0 = 1 , d p i 0 = 1 , d p 0 j = 0 dp00=1, dpi0 = 1, dp0j = 0 dp00=1,dpi0=1,dp0j=0。
- 第四步,递归顺序。一共有两层循环,从前往后进行遍历。
- 第五步,打印结果。
程序如下:
cpp
// 115、不同的子序列-动态规划
class Solution5 {
public:
int numDistinct(string s, string t) {
vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1, 0));
for (int i = 0; i <= s.size(); i++) dp[i][0] = 1; // 第一列初始化为1, dp[0][0]为1
for (int j = 1; j <= t.size(); j++) dp[0][j] = 0; // 第一行初始化为0, 可以省略
for (int i = 1; i <= s.size(); i++) {
for (int j = 1; j <= t.size(); j++) {
if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
else dp[i][j] = dp[i - 1][j];
}
}
return dp[s.size()][t.size()];
}
};
复杂度分析:
- 时间复杂度: O ( n ∗ m ) O(n*m) O(n∗m), n n n和 m m m分别是两个字符串的长度。
- 空间复杂度: O ( n ∗ m ) O(n*m) O(n∗m)。
六、完整代码
cpp
# include <iostream>
# include <vector>
# include <string>
using namespace std;
// 718、最长重复子数组
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int result = 0;
for (int i = 1; i <= nums1.size(); i++) {
for (int j = 1; j <= nums2.size(); j++) {
if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
if (dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
};
// 1143、最长公共子序列
class Solution2 {
public:
int longestCommonSubsequence(string text1, string text2) {
vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
int result = 0;
for (int i = 1; i <= text1.size(); i++) {
for (int j = 1; j <= text2.size(); 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]);
if (dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
};
// 1035、不相交的线-动态规划
class Solution3 {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int result = 0;
for (int i = 1; i <= nums1.size(); i++) {
for (int j = 1; j <= nums2.size(); j++) {
if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
if (dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
};
// 392、判断子序列-动态规划
class Solution4 {
public:
bool isSubsequence(string s, string t) {
vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
int result = 0;
for (int i = 1; i <= s.size(); i++) {
for (int j = 1; j <= t.size(); j++) {
if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = dp[i][j - 1]; // 与1143不同的地方
if (dp[i][j] > result) result = dp[i][j];
}
}
return result == s.size() ? true : false; // 与1143不同的地方
}
};
// 115、不同的子序列-动态规划
class Solution5 {
public:
int numDistinct(string s, string t) {
vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1, 0));
for (int i = 0; i <= s.size(); i++) dp[i][0] = 1; // 第一列初始化为1, dp[0][0]为1
for (int j = 1; j <= t.size(); j++) dp[0][j] = 0; // 第一行初始化为0, 可以省略
for (int i = 1; i <= s.size(); i++) {
for (int j = 1; j <= t.size(); j++) {
if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
else dp[i][j] = dp[i - 1][j];
}
}
return dp[s.size()][t.size()];
}
};
int main() {
//vector<int> nums1 = { 1, 2, 3, 2, 1 }, nums2 = { 3, 2, 1, 4, 7 }; // 测试案例
//Solution s1;
//int result = s1.findLength(nums1, nums2);
//string text1 = "abcde", text2 = "ace"; // 测试案例
//Solution2 s1;
//int result = s1.longestCommonSubsequence(text1, text2);
//vector<int> nums1 = { 1, 4, 2 }, nums2 = { 1, 2, 4 }; // 测试案例
//Solution3 s1;
//int result = s1.maxUncrossedLines(nums1, nums2);
//string s = "abc", t = "ahbgdc"; // 测试案例
//Solution4 s1;
//int result = s1.isSubsequence(s, t);
string s = "babgbag", t = "bag"; // 测试案例
Solution5 s1;
int result = s1.numDistinct(s, t);
cout << result << endl;
system("pause");
return 0;
}
end