Leetcode 1143.最长公共子序列
题目链接:1143 最长公共子序列
题干:给定两个字符串
text1
和text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回0
。一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
思考:动态规划。
- 确定dp数组(dp table)以及下标的含义
dp[i][j]:字符串text1的处理区间[0, i - 1]与字符串text2的处理区间[0, j - 1]中,存在的最长公共子序列的长度。
至于为什么要定义长度为[0, i - 1]的字符串text1,而不是定义为长度为[0, i]的字符串text1。
这主要是为了简化初始化操作,具体理由在 Leetcode 718. 最长重复子数组 中有讲解。
- 确定递推公式
遍历过程就两种情况: text1[i - 1] 与 text2[j - 1]相同 以及 text1[i - 1] 与 text2[j - 1]不相同。
如果text1[i - 1] 与 text2[j - 1]相同,即找到一个公共元素,则在 text1[0, i - 2]与text2[0, j - 2]的最长公共子序列的末尾再加上此次找到的公共元素(即两字符串均缩小处理区间的结果加一)。因此dp[i][j] = dp[i - 1][j - 1] + 1;
如果text1[i - 1] 与 text2[j - 1]不相同,那就比较text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列(即任选一字符串缩小处理区间的结果),取最大的,则dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
- dp数组如何初始化
test1[0, i-1]和空串的最长公共子序列自然是0,所以dp[i][0] = 0;同理dp[0][j]也是0。
其他下标都是随着递推公式逐步覆盖,初始为多少都可以,所以可以统一初始为0。
- 确定遍历顺序
从递推公式,可以看出,有三个方向可以推出dp[i][j],如图:
在递推的过程中,这三个方向都是经过计算的数值,所以要从前向后,从上到下来遍历矩阵。
- 举例推导dp数组
举例:text1 = "abcde", text2 = "ace" ,dp状态如图:
代码:
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
//dp[i][j]:字符串text1的处理区间[0, i - 1]与字符串text2的处理区间[0, j - 1]中,存在的最长公共子序列的长度
vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
for (int i = 1; i <= text1.size(); i++) { //遍历text1字符串
for (int j = 1; j <= text2.size(); j++) { //遍历text2字符串
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[text1.size()][text2.size()];
}
};
Leetcode 1035.不相交的线
题目链接:1035 不相交的线
题干:在两条独立的水平线上按给定的顺序写下
nums1
和nums2
中的整数。现在,可以绘制一些连接两个数字
nums1[i]
和nums2[j]
的直线,这些直线需要同时满足满足:
nums1[i] == nums2[j]
- 且绘制的直线不与任何其他连线(非水平线)相交。
请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。
以这种方法绘制线条,并返回可以绘制的最大连线数。
思考:动态规划。本题如果从怎么判断连线相交的角度思考会卡壳,如果将不相交理解为前后连线元素的相对顺序和数组的相对顺序一致,可以发现本题和上题一模一样。
直线不能相交,这就是说明在字符串A中 找到一个与字符串B相同的子序列,且这个子序列不能改变相对顺序,只要相对顺序不改变,链接相同数字的直线就不会相交。
因此本题说是求绘制的最大连线数,其实就是求两个字符串的最长公共子序列的长度。
代码:
class Solution {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
//dp[i][j]: 数组nums1的处理区间[0, i - 1]与数组nums2的处理区间[0, j - 1]中,存在的最大连线数
vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
for (int i = 1; i <= nums1.size(); i++) { //遍历数组nums1
for (int j = 1; j <= nums2.size(); j++){ //遍历数组nums2
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]);
}
}
return dp[nums1.size()][nums2.size()];
}
};
Leetcode 53. 最大子序和
题目链接:53 最大子序和
题干:给你一个整数数组
nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。
思考:此题的贪心法解法:53. 最大子序和。下面为动态规划解法。
- 确定dp数组(dp table)以及下标的含义
dp[i]:包括下标i(以nums[i]为结尾)的最大连续子序列和。
- 确定递推公式
从dp[i]的定义,可以看出dp[i]只有两个方向可以推出:
- dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和
- nums[i],即:从头开始计算当前连续子序列和
并且要取最大的,所以dp[i] = max(dp[i - 1] + nums[i], nums[i]);
- dp数组如何初始化
从递推公式可以看出来dp[i]是依赖于dp[i - 1]的状态,dp[0]就是递推公式的基础。
根据dp[i]的定义,明显dp[0]应为nums[0]即dp[0] = nums[0]。
- 确定遍历顺序
递推公式中dp[i]依赖于dp[i - 1]的状态,需要从前向后遍历。
- 举例推导dp数组
举例:nums = [-2,1,-3,4,-1,2,1,-5,4],对应的dp状态如下:
要找最大的连续子序列和,就应该比较每一个i为终点的连续最大子序列和。取其中最大值。
代码:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int> dp(nums.size()); //dp[i]:包括下标i(以nums[i]为结尾)的最大连续子序列和
dp[0] = nums[0];
int result = dp[0]; //记录最大和
for (int i = 1; i < nums.size(); i++) {
dp[i] = max(dp[i - 1] + nums[i], nums[i]); //递推公式
if (dp[i] > result) result = dp[i];
}
return result;
}
};
自我总结:
最长公共子序列和还没理解清晰。