646. Maximum Length of Pair Chain
找到符合規則的最長chain。
weekly contest 42的第二題。
我一看感覺很簡單,就是dfs,但是總也寫不出,各種Wrong Answer。
最后想了一下,這個就是類似Permutation的「排列」問題。晚上整理了一下,寫了下面的代碼,總算不Wrong Answer了,但是TLE了,不過思路是比較清晰的:
我的DFS解法(TLE):
//注意,以下代碼會TLE:
int max = 1;
public int findLongestChain(int[][] pairs) {
if (pairs == null || pairs.length == 0) return 0;
chainDfs(pairs, new boolean[pairs.length], 0, Integer.MIN_VALUE);
return max;
}
private void chainDfs(int[][] pairs, boolean[] used, int count, int b) {
//不需要terminator
for (int i = 0; i < pairs.length; i++) {
if (!used[i] && pairs[i][0] > b) {
used[i] = true;
max = Math.max(count + 1, max);
chainDfs(pairs, used, count + 1, pairs[i][1]);
used[i] = false;
}
}
}
既然TLE了,就說明有可以優化的地方。
這題有個條件是In every pair, the first number is always smaller than the second number. 這說明,如果我們以第一個數字排序,那后一個數組[a,b]的第二個數字(b)是一定大于前一個數組[c,d]的的第一個數字(c)的。這樣可以減少很多次遞歸。
優化了一下:
int max = 1;
public int findLongestChain(int[][] pairs) {
if (pairs == null || pairs.length == 0) return 0;
// Arrays.sort(pairs, (a, b) -> a[1] - b[1]);
Arrays.sort(pairs, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[0] - o2[0];
}
});
chainDfs(0, pairs, new boolean[pairs.length], 0, Integer.MIN_VALUE);
return max;
}
private void chainDfs(int index, int[][] pairs, boolean[] used, int count, int b) {
if (index == pairs.length)
return;
for (int i = index; i < pairs.length; i++) {
if (!used[i] && pairs[i][0] > b) {
used[i] = true;
max = Math.max(count + 1, max);
chainDfs(i + 1, pairs, used, count + 1, pairs[i][1]);
used[i] = false;
}
}
}
可是萬萬沒想到,仍然TLE...test case確實非常大,用DFS的話棧深度不可想象。
看了下大家的discussion,有兩種算法,一種是Greedy,說是跟CLRS上的activity selection幾乎一模一樣(還有interval 問題)。。頓時覺得自己基礎差。
另一種是DP。今天先到這兒了。
--
26 July Update
GREEDY(ITERATION)
復習了一下activity selection問題,發現這題跟那個幾乎是一致的。寫了一下代碼,但是我其實并不能說服自己greedy得到的答案就是正確的,這一點跟dp不太一樣,dp是有理有據的,而greedy仿佛是需要證明的,據說greedy經常用剪枝來證明,fine,又一個我認識他他不認識我的名詞。。今天先到這,這題還沒完。
public int findLongestChain(int[][] pairs) {
if (pairs == null || pairs.length == 0) return 0;
int count = 1;
Arrays.sort(pairs, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[1] - o2[1];
}
});
int index = 0;
for (int i = 1; i < pairs.length; i++) {
if (pairs[i][0] > pairs[index][1]) {
count++;
index = i;
}
}
return count;
}
27 July Update
GREEDY(RECURSION)
看了算法導論上的活動選擇問題,這題還可以用遞歸來做,或者說,所有的for循環都能改成遞歸?
這題的遞歸實現:
public int findLongestChain(int[][] pairs) {
if (pairs == null || pairs.length == 0) return 0;
Arrays.sort(pairs, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[1] - o2[1];
}
});
recursion(pairs, 0);
return cnt;
}
int cnt = 1;
private void recursion(int[][] pairs, int index) {
int m = index + 1;
while (m < pairs.length && pairs[index][1] >= pairs[m][0]) {
m++;
}
if (m < pairs.length) {
cnt++;
recursion(pairs, m);
}
}
DP
看了另外有人用DP做的,但是這題用DP有點浪費了,它這個方法跟Weighted Job Scheduling Dynamic Programming的思路是一致的,工作安排比這個多一個要考慮的地方,就是不同的工作既要不沖突,而且因為每份工作的報酬不一樣,不能簡單地只考慮完成時間最短,所以只能用DP。那這題跟Job Schedule唯一的不同就是在if判斷的地方,這題是dp[i] < dp[j] + 1,如果是Job Schedule就要是dp[i] < dp[j] + profit[i]。
兩重循環的解法讓人想起LIS PROBLEM(JOB SCHEDULE跟LIS的判斷方法一模一樣)。
public class Solution {
public int findLongestChain(int[][] pairs) {
Arrays.sort(pairs, (a, b) -> (a[0] - b[0]));
int i, j, max = 0, n = pairs.length;
int dp[] = new int[n];
for (i = 0; i < n; i++) dp[i] = 1;
for (i = 1; i < n; i++)
for (j = 0; j < i; j++)
if (pairs[i][0] > pairs[j][1] && dp[i] < dp[j] + 1)
dp[i] = dp[j] + 1;
for (i = 0; i < n; i++) if (max < dp[i]) max = dp[i];
return max;
}
}
疑問:用DP為什么要sort,為什么按照開始時間和結束時間sort都能AC,不sort就無法AC?
另外,這題的GREEDY,我還是不太懂得如何證明,算法導論上有證明,我沒細看。據說通常用剪枝證明GREEDY。
ref:
http://www.cnblogs.com/hapjin/p/5573419.html
http://www.lxweimin.com/p/293a4056a50a