题目链接:leetcode.cn/problems/lo…
题目
给你一个整数数组 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
提示:
解析
1.状态表示
这题需要用到动态规划来解
dp[i]:最长递增子序列的长度
如上图,dp 表的下标位置和 arr 数组对应,
dp[0]就代表 i = 0时的最大递增子序列长度,就为 1;
当 i = 5时也就是 do[5] = 3,就代表最大递增子序列长度为 3……
2.状态转移方程
那我们如何来维护这个表呢?
我们看上图 dp[i] 的表达有两种
先解释一下j,将它定义在此的目的就是为了表达在i前面的一个数。
1.当arr[i]比前面任何数都小时,它只能为1,像i = 1, i = 2,也就是任何i前面的数 arr[j]都大于arr[i];
2.如果i下标之前存在有最大的dp[j],且满足arr[j] < arr[i],那么就是用第二个表达式,这么讲有点难读,我们直接上例子
拿上图的arr[5] = 7来说,它前面有5个 dp[j],分别是dp[0] = 1,dp[1] = 1,dp[2] = 1,dp[3] = 2,dp[4] = 2
其中 dp[3]和dp[4]都等于2,那么我们肯定拿arr对应值小一点的 (因为小一点的值比大一点的值能续上这个递增序列的概率更大,比如如果后面不是7,而是4的话) 。dp[4](此时j = 4)满足 长度 2 是表中较大的元素,且arr[j]也就是arr[4] = 3 小于 arr[i] = 7 。同时满足这两个条件,就是用第二个表达式。
3.代码
时间复杂度:
空间复杂度:
class Solution {
public int lengthOfLIS(int[] nums) {
//获取数组长度
int n = nums.length;
//1.定义dp表
int[] dp = new int[n];
/*2.初始化dp表
此时为什么要初始化为1,我们不初始化默认的都是0,
初始化就是因为dp[i]最小值为1(最小的递增子序列长度就是1)
而且都初始化为1,就不需要再额外赋值dp表长度为1的情况,
看到下面的内循环就懂了
*/
for (int i = 0; i < n; i++) {
dp[i] = 1;
}
//为了提高代码效率,我们直接在此处定义最大数,待会在循环中更新就行
int maxLength = 1;
//3.核心代码,使用状态转移方程维护dp表
for (int i = 0; i < n; i++) {//此处的外循环是为了给dp表赋值
for (int j = 0; j < i; j++) {//内循环是为了找到dp[i]为多少
//使用我们刚刚判断的条件,如果满足第二个表达式的话就能求出dp[i]
if (nums[j] < nums[i]) {//if中的是判断是否满足前面的数比后面的数小
/*此处是使用for循环,找到 0 ~ i-1 这个范围中 满足if中条件的 最大的dp[j]
然后更新此处的dp[i]
*/
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
}
//在外循环中更新 maxLength 的最大值
maxLength = Math.max(dp[i] , maxLength);
}
return maxLength;
}
}