给定一个整数序列,找到最长上升子序列(LIS),返回LIS的长度。
在线评测地址:LintCode 领扣
说明
最长上升子序列的定义:
最长上升子序列问题是在一个无序的给定序列中找到一个尽可能长的由低到高排列的子序列,这种子序列不一定是连续的或者唯一的。
https://en.wikipedia.org/wiki/Longest_increasing_subsequence
样例 1:
输入: [5,4,1,2,3]
输出: 3
解释:
LIS 是 [1,2,3]
样例 2:
输入: [4,2,4,5,3,7]
输出: 4
解释:
LIS 是 [2,4,5,7
算法:动态规划(dp)
算法思路
- 因为所求为子序列,很容易想到一种线性动态规划。
- 对于求最长上升子序列,上升我们肯定需要知道当前阶段最后一个元素为多少,最长我们还要知道当前我们的序列有多长。
- 那么我们就可以这样定义状态:设
dp[i]
表示以nums[i]
为结尾的最长上升子序列的长度,为了保证元素单调递增肯定只能从i
前面且末尾元素比nums[i]
小的状态转移过来
代码思路
- 状态转移方程为
![v2-b81353daa96961f027cb7a2bb2e89191_b.jpg](https://img-blog.csdnimg.cn/img_convert/bd6e1c36db07e655f84c029b1e722b71.png)
- 每个位置初始值为
dp[i]=1
(将自己作为一个序列) - 答案可以是任何阶段中只要长度最长的那一个,所以我们边转移边统计答案
复杂度分析
N表示为nums
的长度
- 空间复杂度:O(N)
- 时间复杂度:O(N^2)
public class Solution {
/**
* @param nums: The integer array
* @return: The length of LIS (longest increasing subsequence)
*/
public int longestIncreasingSubsequence(int[] nums) {
// dp[i]表示以nums[i]为结尾的最长上升子序列的长度
int []dp = new int[nums.length];
int ans = 0;
for (int i = 0; i < nums.length; i++) {
// 初始值为1
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
// 若nums[j] < nums[i]那么可以接在该序列后,更新状态
dp[i] = dp[i] > dp[j] + 1 ? dp[i] : dp[j] + 1;
}
}
// 记录所有状态中的最大值
if (dp[i] > ans) {
ans = dp[i];
}
}
return ans;
}
}
优化算法:动态规划(dp)+二分优化
上面说过:我们肯定要知道当前阶段最后一个元素为多少,还有当前的序列有多长。上面的方法是用前者做状态,即元素是多少,那么我们可不可以用后者,即序列长度做状态呢?
算法思路
- 设
dp[i]
表示长度为i
的最长上升子序列的末尾元素的最小值,显然这个数组的权值一定单调不降。 - 于是我们按顺序枚举数组
nums
,每一次对dp
数组二分查找,找到小于nums[i]
的最大的dp[j]
,并更新dp[j+1]
。
复杂度分析
- 空间复杂度:O(N)
- 时间复杂度:O(NlogN)
public class Solution {
/**
* @param nums: An integer array
* @return: The length of LIS (longest increasing subsequence)
*/
public int longestIncreasingSubsequence(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] lis = new int[nums.length + 1];
lis[0] = Integer.MIN_VALUE;
for (int i = 1; i <= nums.length; i++) {
lis[i] = Integer.MAX_VALUE;
}
int longest = 0;
for (int i = 0; i < nums.length; i++) {
int index = firstGTE(lis, nums[i]);
lis[index] = nums[i];
longest = Math.max(longest, index);
}
return longest;
}
// GTE = Greater Than or Equal to
private int firstGTE(int[] nums, int target) {
int start = 0, end = nums.length - 1;
while (start + 1 < end) {
int mid = start + (end - start) / 2;
if (nums[mid] >= target) {
end = mid;
} else {
start = mid;
}
}
if (nums[start] >= target) {
return start;
}
return end;
}
}
更多题解参见:九章算法