A clear explanation of maximizing uncrossed connecting lines between two arrays using longest common subsequence dynamic programming.
Problem Restatement
We are given two integer arrays nums1 and nums2.
We draw lines connecting equal values: nums1[i] to nums2[j] when nums1[i] == nums2[j].
Two lines cannot cross.
We need to return the maximum number of connecting lines we can draw without any crossing.
The official constraints state that 1 <= nums1.length, nums2.length <= 500 and 1 <= nums1[i], nums2[j] <= 2000.
Input and Output
| Item | Meaning |
|---|---|
| Input | Arrays nums1 and nums2 |
| Output | Maximum number of uncrossed lines |
Function shape:
def maxUncrossedLines(nums1: list[int], nums2: list[int]) -> int:
...Examples
Example 1:
nums1 = [1, 4, 2]
nums2 = [1, 2, 4]We can connect 1-1 and 4-4: two lines, and they don’t cross.
Answer:
2Example 2:
nums1 = [2, 5, 1, 2, 5]
nums2 = [10, 5, 2, 1, 5, 2]Maximum uncrossed lines: 3.
Answer:
3Key Insight
Two connecting lines do not cross if and only if the connected pairs form a subsequence in both arrays.
This is exactly the Longest Common Subsequence (LCS) problem.
Algorithm
Standard LCS DP:
dp[i][j] = LCS length of nums1[:i] and nums2[:j]Transition:
- 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]).
Correctness
Two lines from nums1[i] to nums2[j] and nums1[i'] to nums2[j'] do not cross if and only if i < i' implies j < j'. This is exactly the condition for an increasing subsequence — the LCS.
Edge Cases
- Initialize the base states explicitly; most wrong answers come from an implicit zero that should not be allowed.
- Confirm the iteration order matches the recurrence dependencies.
- For optimization DP, separate impossible states from valid states with value
0.
Complexity
Let m = len(nums1) and n = len(nums2).
| Metric | Value | Why |
|---|---|---|
| Time | O(m * n) | Fill the DP table |
| Space | O(m * n) | DP table (or O(n) with rolling array) |
Common Pitfalls
- Do not overwrite a state before all transitions that still need the old value have used it.
- Use the problem constraints to choose between full tables and compressed state.
Implementation
class Solution:
def maxUncrossedLines(self, nums1: list[int], nums2: list[int]) -> int:
m, n = len(nums1), len(nums2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
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[m][n]Testing
def run_tests():
s = Solution()
assert s.maxUncrossedLines([1,4,2], [1,2,4]) == 2
assert s.maxUncrossedLines([2,5,1,2,5], [10,5,2,1,5,2]) == 3
assert s.maxUncrossedLines([1,3,7,1,7,5], [1,9,2,5,1]) == 2
print("all tests passed")
run_tests()| Test | Expected | Why |
|---|---|---|
[1,4,2] vs [1,2,4] | 2 | LCS is [1,4] or [1,2] |
| 5 vs 6 elements | 3 | Three common elements in order |
| 6 vs 5 elements | 2 | LCS of length 2 |