LeetCode算法日记 - Day 102: 不相交的线

目录

[1. 不相交的线](#1. 不相交的线)

[1.1 题目解析](#1.1 题目解析)

[1.2 解法](#1.2 解法)

[1.3 代码实现](#1.3 代码实现)


1. 不相交的线

在两条独立的水平线上按给定的顺序写下 nums1nums2 中的整数。

现在,可以绘制一些连接两个数字 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, nums2.length <= 500
  • 1 <= nums1[i], nums2[j] <= 2000

1.1 题目解析

题目本质

这是一个最长公共子序列(LCS)的变形问题。题目要求在两个数组间连线,使得连线不相交且连线数量最大化。

常规解法

最直观的想法是使用回溯法,尝试所有可能的连线组合,对每种组合检查是否存在相交,然后取连线数量的最大值。

java 复制代码
class Solution {
    private int maxLines = 0;
    
    public int maxUncrossedLines(int[] nums1, int[] nums2) {
        backtrack(nums1, nums2, 0, 0, 0);
        return maxLines;
    }
    
    private void backtrack(int[] nums1, int[] nums2, int i, int j, int lines) {
        if (i >= nums1.length || j >= nums2.length) {
            maxLines = Math.max(maxLines, lines);
            return;
        }
        
        // 不连当前位置
        backtrack(nums1, nums2, i + 1, j, lines);
        backtrack(nums1, nums2, i, j + 1, lines);
        
        // 如果当前位置可以连线
        if (nums1[i] == nums2[j]) {
            backtrack(nums1, nums2, i + 1, j + 1, lines + 1);
        }
    }
}

问题分析

回溯法的时间复杂度是指数级的O(2^(m+n)),当数组长度达到500时会超时。需要找到更高效的解法。

思路转折

关键洞察是不相交的连线等价于最长公共子序列。如果我们按照nums1和nums2的顺序依次考虑每个元素,那么不相交的连线必然保持相对顺序不变。要想高效求解 → 必须使用动态规划 → 转化为LCS问题。

1.2 解法

算法思想

使用动态规划求解最长公共子序列。定义dpij表示nums1前i个元素和nums2前j个元素能连接的最大线数。

递推公式:

  • 如果nums1i-1 == nums2j-1:dpij = dpi-1j-1 + 1

  • 否则:dpij = max(dpi-1j, dpij-1)

步骤拆解

**i)**创建二维DP数组,大小为(n+1) × (m+1),初始化为0

**ii)**双重循环遍历两个数组

**iii)**如果当前元素相等,从左上角转移并加1

**iv)**如果不相等,从上方或左方取最大值

**v)**返回dpnm作为最终答案

易错点

  • **数组索引问题:**DP数组是(n+1) × (m+1)大小,但原数组访问时要用i-1和j-1

  • **边界初始化:**第0行和第0列应该保持为0,表示空数组的情况

  • **状态转移理解:**要明确dpi-1j和dpij-1分别表示跳过当前nums1或nums2元素的情况

1.3 代码实现

java 复制代码
class Solution {
    public int maxUncrossedLines(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int m = nums2.length;
        int[][] dp = new int[n + 1][m + 1];
        
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    // 相等时从对角线转移并加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[n][m];
    }
}

复杂度分析

  • **时间复杂度:O(n × m),**需要填充整个DP表格

  • 空间复杂度:O(n × m),使用了二维DP数组存储状态

相关推荐
徐凤年_15 分钟前
rog_map参数理解
算法
春日见16 分钟前
算法与数据结构----哈希表
数据结构·人工智能·算法·机器学习·自动驾驶·哈希算法·散列表
萌动的小火苗44 分钟前
嵌入式开发中的栈与队列:任务调度为什么依赖数据结构
数据结构·c++·单片机·嵌入式硬件
叩码以求索1 小时前
统计按位或能得到最大值的子集数目(一)
数据结构·算法
tachibana21 小时前
hot100 数组中的第K个最大元素(215)
java·数据结构·算法·leetcode
txzrxz1 小时前
单调队列讲解
数据结构·c++·算法·单调队列
不会就选b2 小时前
算法日常・每日刷题--<快排>4
算法
Keven_112 小时前
算法札记:树状数组的用途
数据结构·算法
用户677437175812 小时前
C++函数参数传递方式详解:string、string&、const string、const string&该怎么选?
算法
cpp_25013 小时前
P1540 [NOIP 2010 提高组] 机器翻译
数据结构·c++·算法·队列·noip·洛谷题解