LeetCode算法日记 - Day 91: 最长数对链
目录
1. 最长数对链
1.1 题目解析
1.2 解法
1.3 代码实现
1. 最长数对链
给你一个由 n 个数对组成的数对数组 pairs ,其中 pairs[i] = [lefti, righti] 且 lefti < righti 。
现在,我们定义一种 跟随 关系,当且仅当 b < c 时,数对 p2 = [c, d] 才可以跟在 p1 = [a, b] 后面。我们用这种形式来构造 数对链 。
找出并返回能够形成的 最长数对链的长度 。
你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。
示例 1:
输入:pairs = [[1,2], [2,3], [3,4]] 输出:2 解释:最长的数对链是 [1,2] -> [3,4] 。
示例 2:
输入:pairs = [[1,2],[7,8],[4,5]] 输出:3 解释:最长的数对链是 [1,2] -> [4,5] -> [7,8] 。
提示:
n == pairs.length1 <= n <= 1000-1000 <= lefti < righti <= 1000
1.1 题目解析
题目本质
 这是一个"最长递增子序列"的变体问题。给定多个区间(数对)(i - j),要求选出最多的区间,使得它们能按照"前一个区间的右端点 < 后一个区间的左端点"的规则连接成链。
常规解法
 最直观的想法是枚举所有可能的数对组合,检查哪些能形成合法的链,然后找出最长的那条。可以用回溯或暴力搜索来实现。
class Solution {private int maxLen = 0;public int findLongestChain(int[][] pairs) {boolean[] used = new boolean[pairs.length];backtrack(pairs, used, -1001, 0);return maxLen;}private void backtrack(int[][] pairs, boolean[] used, int lastRight, int len) {maxLen = Math.max(maxLen, len);for(int i = 0; i < pairs.length; i++) {if(!used[i] && pairs[i][0] > lastRight) {used[i] = true;backtrack(pairs, used, pairs[i][1], len + 1);used[i] = false;}}}} 
问题分析
 暴力枚举的时间复杂度是 O(2^n),当 n=1000 时,会产生天文数字级别的计算量,完全不可行。我们需要找到问题的"最优子结构"来避免重复计算。
思路转折
 观察到:如果我们已经知道"以某个数对结尾的最长链长度",那么后续数对只需要判断能否连接到这个数对上即可。
 这提示我们可以用动态规划:将大问题分解为"以第 i 个数对结尾的最长链"的子问题。但前提是要先排序,这样才能保证遍历时已经处理过所有可能的前驱。更进一步,这个问题类似"活动选择问题",可以用贪心算法达到更优的 O(n log n) 复杂度。
1.2 解法
算法思想
采用动态规划 + 区间排序:
 先按右端点升序排序(贪心思想:右端点越小,后面能接的数对越多)
 定义 dp[i] = 以 pairs[i] 结尾的最长链长度状
态转移方程:
dp[i] = Math.max(dp[i], dp[j] + 1); 
i)排序:按右端点升序排列所有数对
ii)初始化:dp[i] = 1(每个数对单独都能组成长度为 1 的链)
iii)状态转移:
-  
对于每个 pairs[i],向前遍历所有 pairs[j](j < i)
 
-  
如果 pairs[j][1] < pairs[i][0],说明 pairs[i] 可以接在 pairs[j] 后面
 
-  
更新 dp[i] = max(dp[i], dp[j] + 1)
 
iv)求答案:遍历 dp 数组,找最大值
易错点
-  
排序依据:必须按右端点排序,而不是左端点。因为右端点越小,给后续数对留的空间越大(贪心思想)
 
-  
连接条件:是 pairs[j][1] < pairs[i][0],注意是严格小于,不能等于。题目定义的跟随关系是 b < c
 
-  
初始化:dp[i] 初始化为 1,不是 0。因为每个数对单独都能组成一条长度为 1 的链
 
-  
遍历顺序:外层循环从 1 开始(因为 dp[0] 已经是 1),内层循环从 0 到 i-1
 
1.3 代码实现
// 最长数对链
static class Solution {public int findLongestChain(int[][] pairs) {int n = pairs.length;// 按右端点排序(贪心:右端点越小,后面能接的越多)Arrays.sort(pairs, (a, b) -> a[1] - b[1]);// dp[i] 表示以 pairs[i] 结尾的最长链长度int[] dp = new int[n];Arrays.fill(dp, 1);// 状态转移for (int i = 1; i < n; i++) {for (int j = 0; j < i; j++) {// 如果 pairs[i] 能接在 pairs[j] 后面if (pairs[j][1] < pairs[i][0]) {dp[i] = Math.max(dp[i], dp[j] + 1);}}}// 找最大值int ret = 1;for (int i = 0; i < n; i++) {ret = Math.max(ret, dp[i]);}return ret;}
} 
复杂度分析
-  
时间复杂度:O(n²)
 
-  
空间复杂度:O(n)
 
