算法:最长递增子序列解法记录
题目
给你一个整数数组 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
题目分析
给定一个整数数组 nums
,要求找到其中最长的严格递增子序列的长度。严格递增意味着每个数都比前一个数大,也就是说序列中的每个元素都要比其前面的元素大。
举个例子,假设 nums = [10, 9, 2, 5, 3, 7, 101, 18]
,其中的最长严格递增子序列为 [2, 3, 7, 101]
,所以结果就是 4
,也就是该子序列的长度。
思路
子序列的定义:子序列是数组中任意几个元素的顺序组合,可以不连续。比如,在
[3, 6, 2, 7]
中,[3, 2, 7]
或者[6, 7]
都是子序列。递增的定义:严格递增的子序列要求每一个数字都比前一个大,所以如果一个子序列是
[x1, x2, ..., xn]
,那么必须满足x1 < x2 < ... < xn
。动态规划的思路:我们可以使用动态规划来解决这个问题。我们定义一个数组
dp
,其中dp[i]
表示以nums[i]
结尾的最长递增子序列的长度。初始时,每个位置的子序列长度至少为 1,因为每个元素本身就是一个递增子序列。递推公式:对于每一个元素
nums[i]
,我们会检查它之前的所有元素nums[j]
,如果nums[i]
大于nums[j]
,那么nums[i]
可以延续以nums[j]
结尾的递增子序列。因此,更新dp[i]
为dp[i] = max(dp[i], dp[j] + 1)
,表示如果以nums[j]
结尾的子序列可以扩展到nums[i]
,则更新dp[i]
的值。最终结果:最长递增子序列的长度就是
dp
数组中的最大值。
详细解读
dp[i]
的定义:dp[i]
表示以nums[i]
这个元素为结尾的最长递增子序列的长度。你可以想象一下,每次考虑到
nums[i]
时,你要确定之前所有比nums[i]
小的元素nums[j]
(j < i
),并且看看是否可以把nums[i]
加到这些子序列的末尾,来形成一个新的递增子序列。每一个位置的初始值是 1,因为至少
nums[i]
本身就是一个递增子序列(长度为 1)。
如何更新
dp[i]
:我们会从每个元素
nums[i]
向前检查所有前面的元素nums[j]
(j < i
)。如果nums[i]
比nums[j]
大,说明nums[i]
可以接在nums[j]
的递增子序列后面,形成一个新的递增子序列。所以,我们尝试更新
dp[i]
的值dp[i] = Math.max(dp[i], dp[j] + 1);
这个式子的意思是:如果
nums[i]
大于nums[j]
,则可以在dp[j]
的基础上延伸一个新的元素nums[i]
,所以dp[i]
可能会变大。dp[j] + 1
:表示以nums[j]
结尾的最长递增子序列长度,再加上当前的nums[i]
。Math.max(dp[i], dp[j] + 1)
:更新dp[i]
,即保留之前的值和新的dp[j] + 1
之间的较大值。
如何求解最终的结果:
最终,我们需要得到 整个数组中最长递增子序列的长度。而这个长度其实就是
dp
数组中的最大值,表示所有以不同元素结尾的最长递增子序列的最大长度。所以,我们用以下方式得到最终结果:
maxLength = Math.max(maxLength, dp[i]);
这会遍历
dp
数组,得到其中的最大值。
举例分析
假设有一个数组 nums = [10, 9, 2, 5, 3, 7, 101, 18]
,我们来看如何通过动态规划得到最长递增子序列的长度。
初始化
dp
数组为:dp = [1, 1, 1, 1, 1, 1, 1, 1]
因为每个元素初始时自己的递增子序列长度为 1。
遍历
nums
数组,更新dp
数组:i = 1, nums[i] = 9,检查所有
j < 1
的元素。没有比 9 小的元素,所以dp[1]
还是 1。i = 2, nums[i] = 2,检查所有
j < 2
的元素。没有比 2 小的元素,所以dp[2]
还是 1。i = 3, nums[i] = 5,检查所有
j < 3
的元素:nums[0] = 10
,nums[0] > nums[3]
,不更新。nums[1] = 9
,nums[1] > nums[3]
,不更新。nums[2] = 2
,nums[2] < nums[3]
,更新dp[3] = dp[2] + 1 = 2
。
i = 4, nums[i] = 3,检查所有
j < 4
的元素:nums[0] = 10
,nums[0] > nums[4]
,不更新。nums[1] = 9
,nums[1] > nums[4]
,不更新。nums[2] = 2
,nums[2] < nums[4]
,更新dp[4] = dp[2] + 1 = 2
。nums[3] = 5
,nums[3] > nums[4]
,不更新。
i = 5, nums[i] = 7,检查所有
j < 5
的元素:nums[0] = 10
,nums[0] > nums[5]
,不更新。nums[1] = 9
,nums[1] > nums[5]
,不更新。nums[2] = 2
,nums[2] < nums[5]
,更新dp[5] = dp[2] + 1 = 2
。nums[3] = 5
,nums[3] < nums[5]
,更新dp[5] = Math.max(dp[5], dp[3] + 1) = 3
。nums[4] = 3
,nums[4] < nums[5]
,更新dp[5] = Math.max(dp[5], dp[4] + 1) = 3
。
继续这个过程直到遍历完整个数组,最终得到
dp = [1, 1, 1, 2, 2, 3, 4, 4]
最终结果:最大的
dp[i]
是4
,因此最长递增子序列的长度是4
。
代码
public class Solution {public int lengthOfLIS(int[] nums) {if (nums == null || nums.length == 0) {return 0; // 如果输入数组为空或没有元素,返回0}// dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度int n = nums.length;int[] dp = new int[n];// 初始时,每个元素的子序列长度为1for (int i = 0; i < n; i++) {dp[i] = 1;}// 遍历每个元素,检查它之前的所有元素for (int i = 1; i < n; i++) {for (int j = 0; j < i; j++) {// 如果 nums[i] 大于 nums[j],那么 nums[i] 可以接在 nums[j] 后面if (nums[i] > nums[j]) {dp[i] = Math.max(dp[i], dp[j] + 1); // 更新 dp[i] 的值}}}// 找到 dp 数组中的最大值,表示最长递增子序列的长度int maxLength = 0;for (int i = 0; i < n; i++) {maxLength = Math.max(maxLength, dp[i]);}return maxLength;}public static void main(String[] args) {Solution solution = new Solution();int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};System.out.println("Longest Increasing Subsequence Length: " + solution.lengthOfLIS(nums));}
}