最长递增子序列 (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;
}