【算法与数据结构】718、1143、1035、392、115、LeetCode最长重复子数组+最长公共子序列+不相交的线+判断子序列+不同的子序列

文章目录

所有的LeetCode题解索引,可以看这篇文章------【算法和数据结构】LeetCode题解

一、718、最长重复子数组

思路分析:

  • 第一步,动态数组的含义。 d p [ i ] [ j ] dp[i][j] dp[i][j]代表以下标 i − 1 i - 1 i−1为结尾的nums1,和以下标 j − 1 j - 1 j−1为结尾的nums2,最长重复子数组长度为 d p [ i ] [ j ] dp[i][j] dp[i][j]。
  • 第二步,递推公式。根据 d p [ i ] [ j ] dp[i][j] dp[i][j]的定义, d p [ i ] [ j ] dp[i][j] dp[i][j]的状态只能由 d p [ i − 1 ] [ j − 1 ] dp[i - 1][j - 1] dp[i−1][j−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 ] dp[i][j] dp[i][j]的最大值记录下来。
      程序如下:
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、最长公共子序列

思路分析:

  1. 第一步,动态数组的含义。 d p [ i ] [ j ] dp[i][j] dp[i][j]代表以下标 i − 1 i - 1 i−1为结尾的text1,和以下标 j − 1 j - 1 j−1为结尾的text2,最长公共子序列长度为 d p [ i ] [ j ] dp[i][j] dp[i][j]。
  2. 第二步,递推公式。 d p [ i ] [ j ] dp[i][j] dp[i][j]可以由两种情况推导出来:
  • t e x t 1 [ i − 1 ] text1[i - 1] text1[i−1]与 t e x t 2 [ j − 1 ] text2[j - 1] text2[j−1]相同:那么找到一个公共元素, d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i - 1][j - 1] + 1 dp[i][j]=dp[i−1][j−1]+1。
  • t e x t 1 [ i − 1 ] text1[i - 1] text1[i−1] 与 t e x t 2 [ j − 1 ] text2[j - 1] text2[j−1]不相同:那么 t e x t 1 [ 0 , i − 2 ] text1[0, i - 2] text1[0,i−2]与 t e x t 2 [ 0 , j − 1 ] text2[0, j - 1] text2[0,j−1]的最长公共子序列和 t e x t 1 [ 0 , i − 1 ] text1[0, i - 1] text1[0,i−1]与 t e x t 2 [ 0 , j − 2 ] text2[0, j - 2] text2[0,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]);
  1. 第三步,元素初始化。dp数组中的所有元素都初始化为0。
  2. 第四步,递归顺序。一共有两层循环,从前往后进行遍历。
  3. 第五步,打印结果。题目要求最长公共子序列的长度。所以在遍历的时候顺便把 d p [ i ] [ j ] dp[i][j] dp[i][j]的最大值记录下来。
      程序如下:
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的长度即可。

  1. 第一步,动态数组的含义。 d p [ i ] [ j ] dp[i][j] dp[i][j]代表以下标 i − 1 i - 1 i−1为结尾的s,和以下标 j − 1 j - 1 j−1为结尾的t,最长公共子序列长度为 d p [ i ] [ j ] dp[i][j] dp[i][j]。
  2. 第二步,递推公式。 d p [ i ] [ j ] dp[i][j] dp[i][j]可以由两种情况推导出来:
  • s [ i − 1 ] s[i - 1] s[i−1]与 t [ j − 1 ] t[j - 1] t[j−1]相同:那么找到一个公共元素, d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i - 1][j - 1] + 1 dp[i][j]=dp[i−1][j−1]+1。
  • s [ i − 1 ] s[i - 1] s[i−1] 与 t [ j − 1 ] t[j - 1] t[j−1]不相同:那么 d p [ i ] [ j ] dp[i][j] dp[i][j]等于 s [ 0 , i − 1 ] s[0, i - 1] s[0,i−1]与 t [ 0 , j − 2 ] t[0, j - 2] t[0,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不同的地方
  1. 第三步,元素初始化。dp数组中的所有元素都初始化为0。
  2. 第四步,递归顺序。一共有两层循环,从前往后进行遍历。
  3. 第五步,打印结果。题目要求最长公共子序列的长度。所以在遍历的时候顺便把 d p [ i ] [ j ] dp[i][j] dp[i][j]的最大值记录下来,在用三目运算符返回。
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相同的方法数量。

  1. 第一步,动态数组的含义。 d p [ i ] [ j ] dp[i][j] dp[i][j]代表以下标 j − 1 j - 1 j−1为结尾的t在以下标 i − 1 i - 1 i−1为结尾的s中出现的次数为 d p [ i ] [ j ] dp[i][j] dp[i][j],即 t [ 0 , j − 1 ] t[0, j-1] t[0,j−1]在 s [ 0 , i − 1 ] s[0, i-1] s[0,i−1]中出现的次数。

  2. 第二步,递推公式。 d p [ i ] [ j ] dp[i][j] dp[i][j]可以由两种情况推导出来:

  • s [ i − 1 ] s[i - 1] s[i−1]与 t [ j − 1 ] t[j - 1] t[j−1]相同:此时的 d p [ i ] [ j ] dp[i][j] dp[i][j]由两部分组成。一部分是用 s [ i − 1 ] s[i-1] s[i−1]来匹配:相当于在 s [ 0 , i − 2 ] s[0, i-2] s[0,i−2]中寻找 t [ 0 , j − 2 ] t[0, j-2] t[0,j−2]的个数(剩下一个字符 s [ i − 1 ] s[i - 1] s[i−1]与 t [ j − 1 ] t[j - 1] t[j−1]已经匹配了),即 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i−1][j−1];另一部分是不用 s [ i − 1 ] s[i-1] s[i−1]来匹配,相当于在 s [ 0 , i − 2 ] s[0, i-2] s[0,i−2]中寻找 t [ 0 , j − 1 ] t[0, j-1] t[0,j−1]的个数,即 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]。
  • s [ i − 1 ] s[i - 1] s[i−1] 与 t [ j − 1 ] t[j - 1] t[j−1]不相同:那么 s [ 0 , i − 2 ] s[0, i - 2] s[0,i−2]中, t [ 0 , j − 1 ] t[0, j - 1] t[0,j−1]的数量和 s [ 0 , i − 1 ] s[0, i - 1] s[0,i−1]中, t [ 0 , j − 1 ] t[0, j - 1] t[0,j−1]的数量相同。 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]
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"。那么用s[4]="g"组成bag的方法数量,相当于在s[0, 3]="bage"中寻找中t[0, 1]="ba"的个数,只有s[0]s[1]s[4]这一种。而不用s[4]="g"组成bag的方法数量,相当于在s[0,3] ="bage"中,寻找t[0,2]="bag"的个数,即dp[4, 3],只有s[0]s[1]s[2]这一种。(说明:dp[4,2]=1代表在s[0,3] ="bage"中,t[0,1]="ba"的个数为1。)

  1. 第三步,元素初始化。 d p [ i ] [ 0 ] dp[i][0] dp[i][0](第一列)表示字符串 s [ 0 , i − 1 ] s[0, i-1] s[0,i−1]中可以随便删除元素,出现空字符串的个数。 d p [ 0 ] [ j ] dp[0][j] dp[0][j](第一行)表示空字符串 s s s,出现字符串 t [ 0 , j − 1 ] t[0, j-1] t[0,j−1]的个数。其中,空字符串s中空字符串t的个数为1。那么 d p [ 0 ] [ 0 ] = 1 , d p [ i ] [ 0 ] = 1 , d p [ 0 ] [ j ] = 0 dp[0][0]=1, dp[i][0] = 1, dp[0][j] = 0 dp[0][0]=1,dp[i][0]=1,dp[0][j]=0。
  2. 第四步,递归顺序。一共有两层循环,从前往后进行遍历。
  3. 第五步,打印结果。
      程序如下:
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

相关推荐
2的n次方_8 分钟前
二维费用背包问题
java·算法·动态规划
simple_ssn37 分钟前
【C语言刷力扣】1502.判断能否形成等差数列
c语言·算法·leetcode
寂静山林1 小时前
UVa 11855 Buzzwords
算法
Curry_Math1 小时前
LeetCode 热题100之技巧关卡
算法·leetcode
ahadee1 小时前
蓝桥杯每日真题 - 第10天
c语言·vscode·算法·蓝桥杯
军训猫猫头2 小时前
35.矩阵格式的一到一百数字 C语言
c语言·算法
Mr_Xuhhh3 小时前
递归搜索与回溯算法
c语言·开发语言·c++·算法·github
SoraLuna3 小时前
「Mac玩转仓颉内测版12」PTA刷题篇3 - L1-003 个位数统计
算法·macos·cangjie
爱吃生蚝的于勒5 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
ChoSeitaku10 小时前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表