本文正在参加「Java主题月 - Java 刷题打卡」,详情查看 活动链接
题目描述
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/un…
在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。
现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足满足:
nums1[i] == nums2[j]- 且绘制的直线不与任何其他连线(非水平线)相交。
请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。
以这种方法绘制线条,并返回可以绘制的最大连线数。
示例 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 <= nums1.length <= 5001 <= nums2.length <= 5001 <= nums1[i], nums2[i] <= 2000
题目分析
这道题初看好像有点麻烦,其实就是最长公共子序列的变形
如果不相交的线有k条,那么nums1中的这k个点与nums2中的k个点,它们的相对顺序是一致的,所以它跟求"abcde"与"ace"这两个字符串的最长公共子序列(最长公共子序列是"ace")是一样一样的
使用动态规划来解,动态规划其实就是记住之前得到的答案,动态规划三步走
- 分解为子问题 动态规划其实就是记住之前得到的答案,对于长度为
m和n的两个序列nums1和nums2,如果nums1的第m项等于nums2的第n项,那么最长公共子序列就是前m-1与n -1项的最长公共子序列加1,所以问题就转化为了求长度为m-1和n-1的两个序列的最长子序列,这样就把问题分解了;同时,如果第m项不等于第n项,此时的最大公共子序列是第m项与第n-1项和第m-1项与第n项中更大的一个 - 状态定义
dp[i][j]表示nums1中前i项与nums2前j项的最长公共子序列 - 状态方程推导 根据上边的分析已经能得到状态转移方程了,如果
nums[i] = nums2[j],那么dp[i][j] = dp[i-1][j-1] + 1;如果nums[i] != nums2[j],那么dp[i][j] = max*(dp[i][j-1], dp[i -1][j]);
public class Solution {
public int maxUncrossedLines(int[] nums1, int[] nums2) {
// dp[i][j] = dp[i-1][j-1] +1
//dp[i][j] = max(dp[i-1][j],dp[i][j-1])
int m = nums1.length;
int n = nums2.length;
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (nums1[i - 1] == nums2[j - 1]) {
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];
}
}
总结
两层循环时间复杂度为O(mn),空间复杂度o(mn)
我把动态规划分为三步,分解为子问题,状态定义,状态方程推导
其关键在于记住之前得到的答案,因为后一步依赖于前一步的结果,必须要把前边的结果记下来,通常使用一个一维数组或者二维数字