1. 题目描述
最长递增子序列(LIS)和最长公共子序列(LCS)都是典型的动态规划问题。
题目:
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
2. 解题思路
首先需要对 子序列 和 子串 这两个概念进行区分!(我之前一直搞错了)
- 【子序列】:并不要求连续,例如上面的示例1中的
[2,3,7,101]
显然就不是连续的,但只要保证输出的这个子序列内没有递减就可以了。 - 【子串】:是原数组、字符串中的子串,不能有间隔。例如示例1中的
[3,7,101]
,2
跟3
之间还隔着个5
,所以2
不算在内。
2.1 定义状态 dp[i]
dp[i]
:以 nums[i]
结尾的最长递增子序列。
(注意,这个不是最终输出的结果,最终输出的应该是所有 dp[i]
中的最大值)
2.2 状态转移方程
我们假设 [0, i-1]
之间的某个数 j
,当最后一个数 i
比第 j
个数大的时候(即 nums[i] > nums[j]
),我们只需要在这个 j
对应的dp数组的值 dp[j
的基础上 +1 ,作为我们的 dp[i]
就行了。
j
有这个多个,选哪个才会使得 dp[i] = dp[j] + 1
最大呢? ——> 用 for
循环把 j
从 0 - j-1
都遍历一次不就知道了。
即,对于每个 i
都循环体内部都有:
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
2.3 初始化
显然,如果数组碰巧都是递减的,那每一个位置上的数,其最长递增子序列都是1。
故,预先对上面的 dp[i]
都fill上1:Arrays.fill(dp, 1)
。
2.4 输出
得到 dp[]
数组后,遍历找出最大值然后 return
即可。
3. 代码(Java)
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int len = nums.length;
// dp[i] 表示:包含第i个元素的最大子序列
int[] dp = new int[len];
Arrays.fill(dp, 1);
// 先计算dp[i]
for (int i = 1; i < len; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
// 再滑动窗口找出dp[] 里面最大的
int ans = 0;
for (int i = 0; i < len; i++) {
ans = Math.max(ans, dp[i]);
}
return ans;
}
}