最长上升子序列
给定一个无序的整数数组,找到其中最长上升子序列的长度。
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
动态规划
- 第 1 步:定义状态。首先考虑“题目问什么,就把什么定义成状态”,发现无从下手。但可以基于下面这个事实,考虑状态的定义:
为了从一个较短的上升子序列得到一个较长的上升子序列,我们主要关心这个较短的上升子序列结尾的元素。
- 为了保证子序列的相对顺序性,在程序读到一个新的数的时候,如果比已经得到的子序列的最后一个数还大,那么就可以放在这个子序列的最后,形成一个更长的子序列。
- 一个子序列一定会以一个数结尾,于是将状态定义成:dp[i] 表示以 nums[i] 结尾的“最长上升子序列”的长度,注意这个定义中 nums[i] 必须被选取,且必须被放在最后一个元素。
- 第 2 步:考虑状态转移方程;
- 遍历到 nums[i] 时,考虑把索引 i 之前的所有的数都看一遍,只要当前的数 nums[i] 严格大于之前的某个数,那么 nums[i] 就可以接在这个数后面形成一个更长的上升子序列。因此,dp[i] 就等于索引 i 之前严格小于 nums[i] 的状态最大者 +1+1。
- 语言描述:在索引 i 之前严格小于 nums[i] 的所有状态中的最大者 + 1+1。
- 符号描述:
- 第 3 步:考虑初始化:dp[0] = 1,1 个字符当然也是长度为 1 的上升子序列;
- 第 4 步:考虑输出:所有 dp[i] 中的最大值(dp[i] 考虑了所有以 nums[i] 结尾的上升子序列);
- 第 5 步:考虑状态压缩:之前所有的状态都得保留,因此无法压缩。
- 时间复杂度O(n^2)
- 空间复杂度O(n)
代码:
public int lengthOfLIS(int[] nums) {
if (nums.length < 2) {
return nums.length;
}
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
for (int i = 1; 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);
}
}
}
int max = -1;
for (int i = 0; i < nums.length; i++) {
max = Math.max(max, dp[i]);
}
return max;
}