最長遞增子序列 (Longest Increasing Subsequence)
Q:在無序的整數數組,找到其中最長上升子序列的長度。
T=O( N2 )
//nums:輸入數組
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
// dp 數組全都初始化爲 1
Arrays.fill(dp, 1);
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j])
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
int res = 0;
for (int i = 0; i < dp.length; i++) {
res = Math.max(res, dp[i]);
}
return res;
}
T=O( N*log(N) )
//蜘蛛紙牌思想
public int lengthOfLIS(int[] nums) {
int[] top = new int[nums.length];
// 牌堆數初始化爲 0
int piles = 0;
for (int i = 0; i < nums.length; i++) {
// 要處理的撲克牌
int poker = nums[i];
/***** 搜索左側邊界的二分查找 *****/
int left = 0, right = piles;
while (left < right) {
int mid = (left + right) / 2;
if (top[mid] > poker) {
right = mid;
} else if (top[mid] < poker) {
left = mid + 1;
} else {
right = mid;
}
}
/*********************************/
// 沒找到合適的牌堆,新建一堆
if (left == piles) piles++;
// 把這張牌放到牌堆頂
top[left] = poker;
}
// 牌堆數就是 LIS 長度
return piles;
}
最長遞增子序列的動態規劃(T=O( N2 )和二分法查找(T=O( N*log(N) )))
俄羅斯套娃
Q:
給定一些標記了寬度和高度的信封,寬度和高度以整數對形式 (w, h) 出現。當另一個信封的 寬度和高度都比這個信封大 的時候,這個信封就可以放進另一個信封裏,請計算 最多能有多少個信封 能組成一組“俄羅斯套娃”信封(即可以把一個信封放到另一個信封裏面)。
思路:
先對 w 排升序,如果 w 相同 h 排降序(逆序排序保證在 w 相同的數對中最多隻選取一個符合條件的 h),然後在所有 h 中尋找最長遞增子序列。
題解:
時間複雜度爲 O( NlogN ),因爲排序和計算 LIS 各需要 O( NlogN )的時間。
空間複雜度爲 O( N ),因爲計算 LIS 的函數中需要一個 top 數組
// envelopes = [[w, h], [w, h]...]:爲輸入的寬高數組
public int maxEnvelopes(int[][] envelopes) {
int n = envelopes.length;
// 按寬度升序排列,如果寬度一樣,則按高度降序排列
Arrays.sort(envelopes, new Comparator<int[]>()
{
public int compare(int[] a, int[] b) {
return a[0] == b[0] ?
b[1] - a[1] : a[0] - b[0];
}
});
// 對高度數組尋找 LIS
int[] height = new int[n];
for (int i = 0; i < n; i++)
height[i] = envelopes[i][1];
return lengthOfLIS(height);
}
/*
二分法查找 lis,T=O( N*logN )
返回 nums 中 LIS 的長度 */
public int lengthOfLIS(int[] nums) {
int piles = 0, n = nums.length;
int[] top = new int[n];
for (int i = 0; i < n; i++) {
// 要處理的撲克牌
int poker = nums[i];
int left = 0, right = piles;
// 二分查找插入位置
while (left < right) {
int mid = (left + right) / 2;
if (top[mid] >= poker)
right = mid;
else
left = mid + 1;
}
if (left == piles) piles++;
// 把這張牌放到牌堆頂
top[left] = poker;
}
// 牌堆數就是 LIS 長度
return piles;
}