LeetCode1035.不相交的线

181 阅读1分钟

「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战

题目

我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。

现在,我们可以绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且我们绘制的直线不与任何其他连线(非水平线)相交。

以这种方法绘制线条,并返回我们可以绘制的最大连线数。

示例 1:

image.png

输入: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, nums2.length <= 500
1 <= nums1[i], nums2[j] <= 2000

解题思路

此题是明显的动态规划题目,而且我们再次考虑题目分析例子,可以发现这道题就是LeetCode1143.最长公共子序列的应用版本(也就是套壳),其思路与LeetCode1143.最长公共子序列完全一致,只需要改改变量名就行。求不想交的最大连线数,其实就是求最长公共子序列

  1. dp[i][j]的定义

dp[i][j]表示表示 nums1 中前 i 个数字 和 nums2 中前 j 个数字最多可以组成的直线数

  1. 状态转移方程
  • 若 nums1 中第 i 个数字和 nums2 中第 j 个数字相同,则让它们匹配可以得到最大的方案数,它们之前的最大方案数为 dp[i-1][j-1]。因此此时,dp[i][j] = dp[i-1][j-1]+1。
  • 若为其它情况,那么 nums1 中第 i 个数字或者 nums2 中第 j 个数字,至少有一个数字不能对方案数做出贡献(也可能两个都不能),因此此时我们需要删除掉其中的一个。若删除nums1 中第 i 个数字,则此时方案数为 dp[i-1][j];若删除nums2 中第 j 个数字,则此时方案数为 dp[i][j-1]。显然,我们应该取这两者中的较大值。也就是 dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
  1. dp数组如何初始化

nums1[0, i-1]和空数组的最长公共子序列自然是0,所以dp[i][0] = 0;同理dp[0][j]也是0。其他下标都是随着递推公式逐步覆盖,初始为多少都可以,那么就统一初始为0。

  1. 确定遍历顺序

从递推公式,可以看出,有三个方向可以推出dp[i][j],左边,左上方,上方,所以为了在递推的过程中,这三个方向都是经过计算的数值,所以要从前向后,从上到下来遍历这个矩阵

var maxUncrossedLines = function(nums1, nums2) {
    let la = nums1.length;
    let lb = nums2.length;
    let dp = Array.from(Array(la + 1), () => Array(lb + 1).fill(0));
    for (let i = 1; i <= la; i++) {
       for (let j = 1; j <= lb; 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[la][lb];
};