Problem: 646. 最长数对链
思路
题目要求寻找一对对数字中能形成的最长“链”,这里的链指的是,后一个数对的首元素必须严格大于前一个数对的尾元素。解决这个问题有两种主要的思路:动态规划和贪心策略。
动态规划
在动态规划的解决方案中,我们先将所有的数对按照第一个元素进行排序,然后使用类似于最长递增子序列(LIS)的方法来计算最长数对链的长度。具体来说,我们维护一个数组ends来存储以当前元素结尾的最长链的结尾值,同时维护一个变量len记录最长链的长度。遍历所有数对,对于每一个数对,我们使用二分查找确定它应该放在ends中的哪个位置,从而保持ends中的值始终递增且长度尽可能短。
贪心策略
贪心策略的核心思想是每次选择结束时间最早的数对。首先,我们将数对按照第二个元素排序,这一步是为了确保我们总是优先选择结束时间更早的数对。然后我们初始化一个变量pre表示链中最后一个数对的结束值,以及一个变量ans来计数链的长度。遍历排序后的数对,只要当前数对的起始值大于pre,说明它可以加入到链中,此时更新pre为当前数对的结束值,并增加ans的计数。
解题方法
动态规划
对数对按第一个元素排序。
使用类似LIS的算法维护一个ends数组和长度len。
遍历数对,使用二分查找确定位置并更新ends和len。
贪心策略
对数对按第二个元素排序。
初始化pre和ans。
遍历数对,如果当前数对起始值大于pre则更新pre和增加ans。
复杂度
时间复杂度:
对于两种方法都是 O ( n l o g n ) O(nlogn) O(nlogn),其中n是数对的数量。主要消耗来自排序操作。
空间复杂度:
动态规划方法为 O ( n ) O(n) O(n),用于存储ends数组;而贪心策略的空间复杂度为 O ( l o g n ) O(logn) O(logn),主要来自于排序所需的辅助空间。
Code
class Solution {
public int findLongestChain1(int[][] pairs) {
int n = pairs.length;
Arrays.sort(pairs, (a, b) -> a[0] - b[0]);
int[] ends = new int[n];
int len = 0;
for(int i = 0, find; i < n; i++) {
find = bs(ends, len, pairs[i][0]);
if(find == -1) {
ends[len++] = pairs[i][1];
} else {
ends[find] = Math.min(ends[find], pairs[i][1]);
}
}
return len;
}
public int bs(int[] ends, int len, int num) {
int l = 0, r = len - 1, ans = -1, m;
while(l <= r) {
m = (l + r) / 2;
if(ends[m] >= num) {
ans = m;
r = m - 1;
} else {
l = m + 1;
}
}
return ans;
}
public int findLongestChain(int[][] pairs) {
int pre = Integer.MIN_VALUE, ans = 0;
Arrays.sort(pairs, (a, b) -> a[1] - b[1]);
for (int[] pair : pairs) {
if (pre < pair[0]) {
pre = pair[1];
ans++;
}
}
return ans;
}
}