Longest Increasing Subsequence
在一个给定的数组中,找最长的连续增长的序列,序列不必一定要连续。
输入: [10,9,2,5,3,7,101,18]
结果: 4
其实最长的连续序列可能会有多个,比如[2,3,7,18], [2,5,7,101], [2,5.7,18]..
方法一
比较容易想的是传统的DP方法。
- 定义:
dp[i]
- 以i
为连续增长序列的最后一个位置的最长的长度 - 初始化:
dp[i] = 1
。因为每一个位置至少都会连续增长一个数,就是它本身 - 转换方程: dp[i] =
dp[j] + 1
ifj < i
&&array[j] < array[i]
&&dp[j] >= dp[i]
- 结果:
dp[n - 1], n = input_array.length
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int m = nums.length;
int[] dp = new int[m]; //dp[i]: using i as the last position of the input array, dp[i] is the length of the longest increasing subsequence for the subarray
int max = 1;
for (int i = 0; i < m; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i] && dp[j] >= dp[i]) {
dp[i] = dp[j] + 1;
}
}
max = Math.max(max, dp[i]);
}
return max;
}
}
时间复杂度 - O(n ^ 2)
空间复杂度 - O(n)
方法二
上一种方法里,dp[i]
的定义是以i为连续增长序列的最后一个位置的最长的长度,在方法二里我们用一个新的定义方法。
- 定义:
tails[i]
-i
作为连续增长的序列的最后一个位置,tails[i]
是对于length = i + 1
的连续增长的序列里的值是最小的。- 比如我们有输入
[1,3,5,2,8,4,6]
- 对于长度是1的连续增长序列,
[1]
就是这个长度下,最后一位最小的结果 - 对于长度是2的连续增长序列,
[1,2]
就是这个长度下,最后一位最小的结果 - 对于长度是3的连续增长序列,
[1,2,4]
就是这个长度下,最后一位最小的结果 - 对于长度是4的连续增长序列,
[1,2,4,6]
就是这个长度下,最后一位最小的结果 - 这个输入里,不能找到长度是5的连续增长的序列,所以结果就是4。基本上,我们只关心某个特定长度下,能当那个长度的subarray的最后一位最小即可。
- 如果我们将输入数组再增加一位9,我们可以获得一个长度是5的连续增长序列 -
[1,2,4,6,9]
- 而如果我们将输入数组再增加的一位不是9而是3,我们可以得到可以一个跟新的
tails[] - [1,2,3,6]
,即当我们如果想知道知道一个长度为3的连续增长的序列的最后一位的最小值,我们就知道是3 - 再比如我们将输入数组再增加的一位不是3而是5,我们可以得到
[1,2,4,5]
,即当我们如果想知道一个长度为4的连续增长序列的最后一位的最小值,我们就知道是5了。
- 比如我们有输入
- 初始化:初始连续最长的连续增长序列的长度为
0
,初始一个tails数组,数组长度等于输入数组长度 - 转移方程:对于当前的array[i]来说,找到它在tails数组里,在[0, length)范围内的,能跟新tails数组的位置。如果跟新的位置是length的位置,我们就相应的给length+1。
- 结果:final_length
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int m = nums.length, length = 0;
int[] tails = new int[m]; // tails[i]: for a longest increasing sequence, the length of which is (i + 1), tails[i] is the smallest value among all the candidates
for (int i = 0; i < m; i++) {
int position = binarysearch(tails, length, nums[i]);
tails[position] = nums[i];
if (position == length) {
length++;
}
}
return length;
}
private int binarysearch(int[] tails, int length, int target) {
if (length == 0) {
return 0;
}
int start = 0, end = length - 1;
while (start + 1 < end) {
int mid = start + (end - start) / 2;
if (tails[mid] == target) {
return mid;
} else if (tails[mid] < target) {
start = mid;
} else {
end = mid;
}
}
if (target <= tails[start]) {
return start;
}
if (target <= tails[end]) {
return end;
}
return end + 1;
}
}
时间复杂度 - O(nlogn)
空间复杂度 - O(n)
Russian Doll Envelopes
输入是一组信封的长度(w, h)。如果一个信封的长和宽都是比另一个封信的长和宽都要大,我们认为这个信封可以套上另一个信封。问在各种组合多,最多的组合能套多少层?
输入: [[5,4],[6,4],[6,7],[2,3]]
结果: 3
解释: [2,3] => [5,4] => [6,7]
这道题比较tricky的地方是如何给这个输入的二维数组(w, h)排序。最容易想到的是我们先按宽度w按递增的方式排序,如果两封信的宽度w一样,那我们就按长度h再递增的排序。按这种方式排
假设我们有输入 - [[1, 3], [1, 4], [1, 5], [2, 3]],且已经按我们定义的排序方式排好序了。
因为宽度已经是排序了,我们能不能单独把长度这一维度拿出来,转化成一道Longest Increasing Subsequence的问题呢?当我们单独把长度这一维度拿出来时,我们得到[3,4,5,3]
,那最终结果应该是3。但是其实前3个信封并不能依次套入,因为还有宽度相等的情况。
这里如果我们修改一下我们的排序规则,同样还是先按宽度递增排序,如果宽度一样时,我们按长度递减排序。这样我们可以[5,4,3,3]
,因此结果是1。实际的情况就是我们不能把[1,3]套入[1,4],不能把[1,4]套进[1,5]。
剩下的工作就是基本上和上面的LIS的解法一致。
时间复杂度 - O(n ^ 2)的解法
class Solution {
public int maxEnvelopes(int[][] envelopes) {
if (envelopes == null || envelopes.length == 0) {
return 0;
}
// sort envelopes by w accendingly.
// if w is the same, sort envelopes by h decendingly
Arrays.sort(envelopes, (int[] e1, int[] e2) -> {
if (e1[0] != e2[0]) {
return e1[0] - e2[0];
} else {
return e2[1] - e1[1];
}
});
int m = envelopes.length, max = 1;
int[] dp = new int[m];
for (int i = 0; i < m; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (envelopes[i][1] > envelopes[j][1] && dp[j] >= dp[i]) {
dp[i] = dp[j] + 1;
}
}
max = Math.max(max, dp[i]);
}
return max;
}
}
时间复杂度 - O(nlogn)的解法
class Solution {
public int maxEnvelopes(int[][] envelopes) {
if (envelopes == null || envelopes.length == 0) {
return 0;
}
// sort envelopes by w accendingly.
// if w is the same, sort envelopes by h decendingly
Arrays.sort(envelopes, (int[] e1, int[] e2) -> {
if (e1[0] != e2[0]) {
return e1[0] - e2[0];
} else {
return e2[1] - e1[1];
}
});
int m = envelopes.length, length = 0;
int[] tails = new int[m];
for (int[] e: envelopes) {
int position = binarysearch(tails, length, e[1]);
tails[position] = e[1];
if (position == length) {
length++;
}
}
return length;
}
private int binarysearch(int[] tails, int length, int target) {
if (length == 0) {
return 0;
}
int start = 0, end = length - 1;
while (start + 1 < end) {
int mid = start + (end - start) / 2;
if (tails[mid] == target) {
return mid;
} else if (tails[mid] < target) {
start = mid;
} else {
end = mid;
}
}
if (target <= tails[start]) {
return start;
}
if (target <= tails[end]) {
return end;
}
return end + 1;
}
}
网友评论