题目地址:
https://www.lintcode.com/problem/longest-increasing-subsequence/description
给定一个数组 ,求其(严格)最长上升子序列的长度。
法1:动态规划。设 是以 结尾的最长的上升子序列的长度,那么要计算 ,如果 之前有数字 小于 ,则 可以更新为(如果更长的话) ;如果没有数小于 ,则 。以这个递推关系即可递推出所有以 结尾的最长上升子序列的长度。最后只需要遍历 取最大值即可。代码如下:
public class Solution {
/**
* @param nums: An integer array
* @return: The length of LIS (longest increasing subsequence)
*/
public int longestIncreasingSubsequence(int[] nums) {
// write your code here
if (nums == null || nums.length == 0) {
return 0;
}
int[] dp = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
dp[i] = Math.max(dp[i], 1);
}
int res = 0;
for (int i : dp) {
res = Math.max(res, i);
}
return res;
}
}
时间复杂度 ,空间 。
法2:贪心法。我们想方设法来记录末尾数尽量小的上升子序列。遇到一个新数的时候,去找一下它能接在谁的后面。整个数组遍历完后,返回得到的最长的上升子序列的长度即可。严格证明附在后面,代码如下:
public class Solution {
/**
* @param nums: An integer array
* @return: The length of LIS (longest increasing subsequence)
*/
public int longestIncreasingSubsequence(int[] nums) {
// write your code here
if (nums == null || nums.length == 0) {
return 0;
}
// f[i]是长度为i + 1的所有子序列中末尾数最小的那个子序列的末尾数
int[] f = new int[nums.length];
int cur = 0;
f[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
int last = findFirstLargerOrEqual(f, cur, nums[i]);
if (last != -1) {
f[last] = nums[i];
} else if (nums[i] > f[cur]) {
f[++cur] = nums[i];
}
}
return cur + 1;
}
// 找在f[0,...,r]中找第一个大于或等于num的数的下标;不存在则返回-1
private int findFirstLargerOrEqual(int[] f, int r, int num) {
int l = 0;
while (l < r) {
int m = l + (r - l >> 1);
if (f[m] < num) {
l = m + 1;
} else {
r = m;
}
}
if (f[l] > num) {
return l;
} else {
return -1;
}
}
}
时间复杂度 ,空间 。
算法正确性证明:
首先证明
确实存储了长度为
的所有上升子序列中末尾数最小的那个子序列的末尾数,并且若
,说明遍历到当前数为止,未发现长度为
的上升子序列。
为了证明这一点,先证
是严格单调增的,如若不然,则存在
使得
,那么存在以
结尾的长度为
的子序列,此子序列的第
个数是比
要小的,这与
的定义矛盾了。所以
单调增。
接下来使用数学归纳法。一开始遍历数组中第一个数,
初始化为数组第一个数,是没有问题的。假设后面某时刻
都符合定义,接下来遍历到数组中的下一个数比如说是
,如果
,那么
可以接在长度为
且末尾数为
的子序列后面,成为长度为
的新的上升子序列,所以
可以被更新为
;否则,则要在
中寻找第一个大于等于
的数
,并将
更新为
,理由是,由于
单调上升,所以
,所以
可以接到以
结尾的长度为
的上升子序列后,这样就得到了一个长度为
的上升子序列,所以
可以更新为
(当然如果
那结论更显然)。而对于
,
都小于
,所以无法得到更新;而对于
,如果
更新成为
,则会导致可以构造出长度为
且末尾数小于
的上升子序列,与
的定义矛盾。所以新遍历一个数后,
的定义仍然被保持。由数学归纳法,数组遍历完后,
非零数字的个数就是最长上升子序列的长度。