??先來看一下題
??題意:
You have a number of envelopes with widths and heights given as
a pair of integers (w, h). One envelope can fit into another if and
only if both the width and height of one envelope is greater than
the width and height of the other envelope.
What is the maximum number of envelopes can you Russian doll?
(put one inside other)
??樣例:
Given envelopes = [[5,4],[6,4],[6,7],[2,3]],
the maximum number of envelopes you can Russian doll is 3 ([2,3]
=> [5,4] => [6,7]).
??這個(gè)題的大意是這樣的:給定一系列有大小的信封,請(qǐng)問像俄羅斯娃娃裝起來,最多可以裝幾個(gè),相同寬或者高的信封不能裝。
1.動(dòng)態(tài)規(guī)劃(超時(shí))
??剛剛看到這個(gè)題時(shí),想到的是動(dòng)態(tài)規(guī)劃,因?yàn)樽约旱膭?dòng)態(tài)比較薄弱,因此更加想要?jiǎng)討B(tài)規(guī)劃做了。于是乎,經(jīng)過填表,找出了動(dòng)態(tài)規(guī)劃方程。先說一下思想:我們遍歷信封,看看當(dāng)前當(dāng)前要裝其他信封的信封能裝下的最大個(gè)數(shù),說得很抽象,看一張表:
??但是這里需要注意的是:這里信封的順序是條件的,我們必須按照寬(也就是說,第一個(gè)長度)的升序排序,高升序和降序都行。因?yàn)槲覀儽仨毾劝研⌒欧獾淖畲笾登蟪觯竺娲笮欧庠谘b小信封時(shí),取得的最大值才是正確的;否則,我們先按照小信封的最大值(沒有求),求大信封的最大值,如果之后小信封的最大值改變,但是大信封的最大值沒有更新。
public static int maxEnvelopes(int[][] envelopes) {
if (envelopes == null || envelopes.length == 0)
return 0;
//排序,按照寬的升序和高的降序排序(如果寬相同的話,則按寬的高的降序排序)
Arrays.sort(envelopes, (a, b) -> {
if (a[0] != b[0]) {
return a[0] - b[0];
} else {
return a[1] - b[1];
}
});
int max = 1;
int[] a = new int[envelopes.length];
//這里將數(shù)組初始化為1,上圖的表中是0,如果這里初始化為0話,最后max需要加1
Arrays.fill(a, 1);
for (int i = 0; i < envelopes.length; i++) {
for (int j = 0; j < i; j++) {
//當(dāng)符合要求是在選擇更新a表
if (envelopes[j][0] < envelopes[i][0] && envelopes[j][1] < envelopes[i][1]) {
a[i] = Math.max(a[i], 1 + a[j]);
if (a[i] > max) {
max = a[i];
}
}
}
}
return max;
}
2.二分法
??感覺動(dòng)態(tài)已經(jīng)能夠解決了,其實(shí)不然,動(dòng)態(tài)規(guī)劃也超時(shí)了。具體在哪里超時(shí)的,我也不太知道,估計(jì)是在兩重for循環(huán)那里超時(shí)了,后面在網(wǎng)上找到了二分法的解決辦法
(1).前提
??二分法的排序必須按照寬的升序,和高的降序來排。至于為什么呢?后面再說。例如:原來的序列:[[5,4],[6,4],[6,7],[2,3],[6,8],[7,8],[1,3]],排序之后:[[1,3],[2,3],[5,4],[6,8],[6,7],[6,4],[7,8]]
(2).思路
??首先我們用一個(gè)List集合來保存每個(gè)加進(jìn)來信封的高(第二個(gè)數(shù)),當(dāng)要進(jìn)來的那個(gè)信封的高比List集合的最后數(shù)據(jù)大的話,則將這個(gè)高加入List集合,這是因?yàn)榻?jīng)過我們之前的排序,如果一個(gè)信封的高大于前一個(gè)信封的高,必定這個(gè)信封的寬也大于前一個(gè)的寬。可能看不太懂,如圖所示:
??但是如果進(jìn)來的高比最后一個(gè)數(shù)據(jù)小或者等于呢?首先肯定不能加到集合中去,那為什么會(huì)替換(后面代碼中會(huì)有替換的操作)。想一下,我們是按照寬的升序排序,后面加入來的信封肯定大于或者等于前面加進(jìn)去的信封的寬;后面進(jìn)來信封的高小于或者等于時(shí),有兩種情況:寬大于前一個(gè)信封的寬或者等于前面的一個(gè)寬。
?? 二分法來替換的目的是:List集合的數(shù)據(jù)是加進(jìn)來信封的高的升序,同時(shí)寬也是升序的。當(dāng)即將進(jìn)來的信封高小于或者等于前一個(gè)的高,于是在List集合找到它的位置保存下來,相當(dāng)于說,每次加入信封時(shí),我們必須保證List集合的數(shù)據(jù)是按照升序排序的,這樣才能保證更多的加入信封。
??而這里為什么要按照高的降序排序呢?這是為了簡便當(dāng)即將進(jìn)來的高比前一個(gè)的高大的話,這里只有一種可能,如果按照升序排序的話,還要考慮到寬等于的情況。
public static int maxEnvelopes(int[][] envelopes) {
if (envelopes == null || envelopes.length == 0)
return 0;
Arrays.sort(envelopes, new Comparator<int[]>() {
public int compare(int[] a, int[] b) {
if (a[0] != b[0]) {
return a[0] - b[0]; // ascending order
} else {
return b[1] - a[1]; // descending order
}
}
});
ArrayList<Integer> list = new ArrayList<Integer>();
//遍歷每個(gè)信封,相當(dāng)于決定每個(gè)信封的位置
for (int i = 0; i < envelopes.length; i++) {
if (list.size() == 0 || list.get(list.size() - 1) < envelopes[i][1]) {
list.add(envelopes[i][1]);
continue;
}
//二分替換,使得加進(jìn)來的每個(gè)信封的寬在List集合中是升序排序
int l = 0;
int r = list.size() - 1;
while (l < r) {
int m = (l + r) / 2;
if (list.get(m) < envelopes[i][1]) {
l = m + 1;
} else {
r = m;
}
}
list.set(r, envelopes[i][1]);
}
return list.size();
}