前言
代码随想录算法训练营day53
一、Leetcode 1143.最长公共子序列
1.题目
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace" 输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc" 输出:3 解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def" 输出:0 解释:两个字符串没有公共子序列,返回 0 。
提示:
1. 1 <= text1.length, text2.length <= 1000 2. text1 和 text2 仅由小写英文字符组成。
来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/longest-common-subsequence
2.解题思路
方法一:动态规划
最长公共子序列问题是典型的二维动态规划问题。
假设字符串 text1text1 和 text2text2 的长度分别为 mm 和 nn,创建 m+1m+1 行 n+1n+1 列的二维数组 dpdp,其中 dp[i][j]dp[i][j] 表示 text1[0:i]text1[0:i] 和 text2[0:j]text2[0:j] 的最长公共子序列的长度。
上述表示中,text1[0:i]text1[0:i] 表示 text1text1 的长度为 ii 的前缀,text2[0:j]text2[0:j] 表示 text2text2 的长度为 jj 的前缀。
考虑动态规划的边界情况:
1. 当 i=0i=0 时,text1[0:i]text1[0:i] 为空,空字符串和任何字符串的最长公共子序列的长度都是 00,因此对任意 0≤j≤n0≤j≤n,有 dp[0][j]=0dp[0][j]=0; 2. 3. 当 j=0j=0 时,text2[0:j]text2[0:j] 为空,同理可得,对任意 0≤i≤m0≤i≤m,有 dp[i][0]=0dp[i][0]=0。
因此动态规划的边界情况是:当 i=0i=0 或 j=0j=0 时,dp[i][j]=0dp[i][j]=0。
当 i>0i>0 且 j>0j>0 时,考虑 dp[i][j]dp[i][j] 的计算:
1. 当 text1[i−1]=text2[j−1]text1[i−1]=text2[j−1] 时,将这两个相同的字符称为公共字符,考虑 text1[0:i−1]text1[0:i−1] 和 text2[0:j−1]text2[0:j−1] 的最长公共子序列,再增加一个字符(即公共字符)即可得到 text1[0:i]text1[0:i] 和 text2[0:j]text2[0:j] 的最长公共子序列,因此 dp[i][j]=dp[i−1][j−1]+1dp[i][j]=dp[i−1][j−1]+1。 2. 3. 当 text1[i−1]≠text2[j−1]text1[i−1]=text2[j−1] 时,考虑以下两项: 4. 5. text1[0:i−1]text1[0:i−1] 和 text2[0:j]text2[0:j] 的最长公共子序列; 6. 7. text1[0:i]text1[0:i] 和 text2[0:j−1]text2[0:j−1] 的最长公共子序列。 8. 9. 要得到 text1[0:i]text1[0:i] 和 text2[0:j]text2[0:j] 的最长公共子序列,应取两项中的长度较大的一项,因此 dp[i][j]=max(dp[i−1][j],dp[i][j−1])dp[i][j]=max(dp[i−1][j],dp[i][j−1])。
由此可以得到如下状态转移方程:
dp[i][j]={dp[i−1][j−1]+1,text1[i−1]=text2[j−1]max(dp[i−1][j],dp[i][j−1]),text1[i−1]≠text2[j−1]dp[i][j]={dp[i−1][j−1]+1,max(dp[i−1][j],dp[i][j−1]),text1[i−1]=text2[j−1]text1[i−1]=text2[j−1]
最终计算得到 dp[m][n]dp[m][n] 即为 text1text1 和 text2text2 的最长公共子序列的长度。
3.代码实现
```java class Solution { public int longestCommonSubsequence(String text1, String text2) { int m = text1.length(), n = text2.length(); int[][] dp = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { char c1 = text1.charAt(i - 1); for (int j = 1; j <= n; j++) { char c2 = text2.charAt(j - 1); if (c1 == c2) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } return dp[m][n]; } } ```
二、Leetcode 1035.不相交的线
1.题目
在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。
现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足满足:
1. nums1[i] == nums2[j] 2. 且绘制的直线不与任何其他连线(非水平线)相交。
请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。
以这种方法绘制线条,并返回可以绘制的最大连线数。
示例 1:
输入:nums1 = [1,4,2], nums2 = [1,2,4] 输出:2 解释:可以画出两条不交叉的线,如上图所示。 但无法画出第三条不相交的直线,因为从 nums1[1]=4 到 nums2[2]=4 的直线将与从 nums1[2]=2 到 nums2[1]=2 的直线相交。
示例 2:
输入:nums1 = [2,5,1,2,5], nums2 = [10,5,2,1,5,2] 输出:3
示例 3:
输入:nums1 = [1,3,7,1,7,5], nums2 = [1,9,2,5,1] 输出:2
提示:
1. 1 <= nums1.length, nums2.length <= 500 2. 1 <= nums1[i], nums2[j] <= 2000
来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/uncrossed-lines
2.解题思路
方法一:动态规划
给定两个数组 nums1nums1 和 nums2nums2,当 nums1[i]=nums2[j]nums1[i]=nums2[j] 时,可以用一条直线连接 nums1[i]nums1[i] 和 nums2[j]nums2[j]。假设一共绘制了 kk 条互不相交的直线,其中第 xx 条直线连接 nums1[ix]nums1[ix] 和 nums2[jx]nums2[jx],则对于任意 1≤x≤k1≤x≤k 都有 nums1[ix]=nums2[jx]nums1[ix]=nums2[jx],其中 i1
上述 kk 条互不相交的直线分别连接了数组 nums1nums1 和 nums2nums2 的 kk 对相等的元素,而且这 kk 对相等的元素在两个数组中的相对顺序是一致的,因此,这 kk 对相等的元素组成的序列即为数组 nums1nums1 和 nums2nums2 的公共子序列。要计算可以绘制的最大连线数,即为计算数组 nums1nums1 和 nums2nums2 的最长公共子序列的长度。最长公共子序列问题是典型的二维动态规划问题。
假设数组 nums1nums1 和 nums2nums2 的长度分别为 mm 和 nn,创建 m+1m+1 行 n+1n+1 列的二维数组 dpdp,其中 dp[i][j]dp[i][j] 表示 nums1[0:i]nums1[0:i] 和 nums2[0:j]nums2[0:j] 的最长公共子序列的长度。
考虑动态规划的边界情况:
1. 当 i=0i=0 时,nums1[0:i]nums1[0:i] 为空,空数组和任何数组的最长公共子序列的长度都是 00,因此对任意 0≤j≤n0≤j≤n,有 dp[0][j]=0dp[0][j]=0; 2. 3. 当 j=0j=0 时,nums2[0:j]nums2[0:j] 为空,同理可得,对任意 0≤i≤m0≤i≤m,有 dp[i][0]=0dp[i][0]=0。
因此动态规划的边界情况是:当 i=0i=0 或 j=0j=0 时,dp[i][j]=0dp[i][j]=0。
当 i>0i>0 且 j>0j>0 时,考虑 dp[i][j]dp[i][j] 的计算:
1. 当 nums1[i−1]=nums2[j−1]nums1[i−1]=nums2[j−1] 时,将这两个相同的元素称为公共元素,考虑 nums1[0:i−1]nums1[0:i−1] 和 nums2[0:j−1]nums2[0:j−1] 的最长公共子序列,再增加一个元素(即公共元素)即可得到 nums1[0:i]nums1[0:i] 和 nums2[0:j]nums2[0:j] 的最长公共子序列,因此 dp[i][j]=dp[i−1][j−1]+1dp[i][j]=dp[i−1][j−1]+1。 2. 3. 当 nums1[i−1]≠nums2[j−1]nums1[i−1]=nums2[j−1] 时,考虑以下两项: 4. 5. nums1[0:i−1]nums1[0:i−1] 和 nums2[0:j]nums2[0:j] 的最长公共子序列; 6. 7. nums1[0:i]nums1[0:i] 和 nums2[0:j−1]nums2[0:j−1] 的最长公共子序列。 8. 9. 要得到 nums1[0:i]nums1[0:i] 和 nums2[0:j]nums2[0:j] 的最长公共子序列,应取两项中的长度较大的一项,因此 dp[i][j]=max(dp[i−1][j],dp[i][j−1])dp[i][j]=max(dp[i−1][j],dp[i][j−1])。
由此可以得到如下状态转移方程:
dp[i][j]={dp[i−1][j−1]+1,nums1[i−1]=nums2[j−1]max(dp[i−1][j],dp[i][j−1]),nums1[i−1]≠nums2[j−1]dp[i][j]={dp[i−1][j−1]+1,max(dp[i−1][j],dp[i][j−1]),nums1[i−1]=nums2[j−1]nums1[i−1]=nums2[j−1]
最终计算得到 dp[m][n]dp[m][n] 即为数组 nums1nums1 和 nums2nums2 的最长公共子序列的长度,即可以绘制的最大连线数。
fig1
3.代码实现
```java class Solution { public int maxUncrossedLines(int[] nums1, int[] nums2) { int m = nums1.length, n = nums2.length; int[][] dp = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { int num1 = nums1[i - 1]; for (int j = 1; j <= n; j++) { int num2 = nums2[j - 1]; if (num1 == num2) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } return dp[m][n]; } } ```
三、Leetcode 53. 最大子序和
1.题目
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1] 输出:1
示例 3:
输入:nums = [5,4,-1,7,8] 输出:23
提示:
1. 1 <= nums.length <= 105 2. -104 <= nums[i] <= 104
来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/maximum-subarray
2.解题思路
方法一:动态规划
思路和算法
假设 numsnums 数组的长度是 nn,下标从 00 到 n−1n−1。
我们用 f(i)f(i) 代表以第 ii 个数结尾的「连续子数组的最大和」,那么很显然我们要求的答案就是:
max0≤i≤n−1{f(i)}0≤i≤n−1max{f(i)}
因此我们只需要求出每个位置的 f(i)f(i),然后返回 ff 数组中的最大值即可。那么我们如何求 f(i)f(i) 呢?我们可以考虑 nums[i]nums[i] 单独成为一段还是加入 f(i−1)f(i−1) 对应的那一段,这取决于 nums[i]nums[i] 和 f(i−1)+nums[i]f(i−1)+nums[i] 的大小,我们希望获得一个比较大的,于是可以写出这样的动态规划转移方程:
f(i)=max{f(i−1)+nums[i],nums[i]}f(i)=max{f(i−1)+nums[i],nums[i]}
不难给出一个时间复杂度 O(n)O(n)、空间复杂度 O(n)O(n) 的实现,即用一个 ff 数组来保存 f(i)f(i) 的值,用一个循环求出所有 f(i)f(i)。考虑到 f(i)f(i) 只和 f(i−1)f(i−1) 相关,于是我们可以只用一个变量 prepre 来维护对于当前 f(i)f(i) 的 f(i−1)f(i−1) 的值是多少,从而让空间复杂度降低到 O(1)O(1),这有点类似「滚动数组」的思想。
3.代码实现
```java class Solution { public int maxSubArray(int[] nums) { int pre = 0, maxAns = nums[0]; for (int x : nums) { pre = Math.max(pre + x, x); maxAns = Math.max(maxAns, pre); } return maxAns; } }