题目描述
给你一个整数数组 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 <= nums.length <= 2500
-10^4 <= nums[i] <= 10^4
进阶:
- 你能将算法的时间复杂度降低到
O(n log(n))
吗?
解题思路
方法一:动态规划
思路
-
状态定义:
dp[i]
表示以nums[i]
结尾的最长递增子序列长度 -
状态转移:对于每个位置
i
,检查所有前面的位置j
-
如果
nums[j] < nums[i]
,则可以将nums[i]
接在以nums[j]
结尾的序列后面 -
dp[i] = max(dp[i], dp[j] + 1)
-
步骤
-
初始化:每个位置的
dp[i] = 1
(单个元素的长度) -
双重循环:外层遍历每个位置
i
,内层遍历i
之前的所有位置j
-
状态转移:如果
nums[j] < nums[i]
,更新dp[i]
-
返回
dp
数组中的最大值
算法过程
nums = [10, 9, 2, 5, 3, 7, 101, 18]
初始化:dp = [1, 1, 1, 1, 1, 1, 1, 1]
i=1, nums[1]=9:
j=0: nums[0]=10 > 9, 不更新
dp = [1, 1, 1, 1, 1, 1, 1, 1]
i=2, nums[2]=2:
j=0: nums[0]=10 > 2, 不更新
j=1: nums[1]=9 > 2, 不更新
dp = [1, 1, 1, 1, 1, 1, 1, 1]
i=3, nums[3]=5:
j=0: nums[0]=10 > 5, 不更新
j=1: nums[1]=9 > 5, 不更新
j=2: nums[2]=2 < 5, dp[3] = max(1, 1+1) = 2
dp = [1, 1, 1, 2, 1, 1, 1, 1]
...继续此过程...
最终:dp = [1, 1, 1, 2, 2, 3, 4, 4]
最大值:4
代码实现
Java 实现
import java.util.Arrays;
/**
* 300. 最长递增子序列
* 动态规划
*/
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int n = nums.length;
// dp[i] 表示以 nums[i] 结尾的最长递增子序列长度
int[] dp = new int[n];
// 初始化:每个元素单独构成长度为1的子序列
Arrays.fill(dp, 1);
int maxLen = 1; // 记录全局最大长度
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
// 如果 nums[j] < nums[i],可以扩展子序列
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
maxLen = Math.max(maxLen, dp[i]);
}
return maxLen;
}
}
C# 实现
/**
* 300. 最长递增子序列
* 动态规划
*/
public class Solution
{
/**
* 找到数组中最长严格递增子序列的长度
* 使用动态规划方法,时间复杂度O(n^2),空间复杂度O(n)
*
* @param nums 输入的整数数组
* @return 最长递增子序列的长度
*/
public int LengthOfLIS(int[] nums)
{
if (nums == null || nums.Length == 0)
{
return 0;
}
int n = nums.Length;
// dp[i] 表示以 nums[i] 结尾的最长递增子序列长度
int[] dp = new int[n];
// 初始化每个位置的最长递增子序列长度为1(至少包含自己)
Array.Fill(dp, 1);
int maxLen = 1;
// 遍历每个位置,计算以该位置结尾的最长递增子序列长度
for (int i = 1; i < n; i++)
{
// 检查当前位置之前的所有元素
for (int j = 0; j < i; j++)
{
// 如果前面的元素小于当前元素,则可以构成递增序列
if (nums[j] < nums[i])
{
// 更新以当前位置结尾的最长递增子序列长度
dp[i] = Math.Max(dp[i], dp[j] + 1);
}
}
// 更新全局最长递增子序列长度
maxLen = Math.Max(maxLen, dp[i]);
}
return maxLen;
}
}
复杂度分析
-
时间复杂度:O(n²)
-
外层循环 O(n),内层循环 O(n)
-
总体时间复杂度 O(n²)
-
-
空间复杂度:O(n)
-
需要 dp 数组存储状态
-
方法二:二分查找优化
主要思路
-
维护一个
tails
数组,tails[i]
表示长度为i+1
的递增子序列的最小末尾元素 -
对于每个新元素,使用二分查找找到合适的插入位置
-
这样可以确保
tails
数组始终保持递增状态
算法过程
nums = [10, 9, 2, 5, 3, 7, 101, 18]
tails = []
处理 10: tails = [10], size = 1
处理 9: 二分查找位置0, tails = [9], size = 1
处理 2: 二分查找位置0, tails = [2], size = 1
处理 5: 二分查找位置1, tails = [2, 5], size = 2
处理 3: 二分查找位置1, tails = [2, 3], size = 2
处理 7: 二分查找位置2, tails = [2, 3, 7], size = 3
处理101: 二分查找位置3, tails = [2, 3, 7, 101], size = 4
处理 18: 二分查找位置3, tails = [2, 3, 7, 18], size = 4
最终长度:4
代码实现
Java 实现
/**
* 300. 最长递增子序列
* 二分查找
*/
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
// tails[i] 表示长度为 i+1 的递增子序列的最小末尾元素
int[] tails = new int[nums.length];
int size = 0; // tails 数组的有效长度
for (int num : nums) {
// 二分查找第一个 >= num 的位置
int left = 0, right = size;
while (left < right) {
int mid = left + (right - left) / 2;
if (tails[mid] < num) {
left = mid + 1;
} else {
right = mid;
}
}
// 更新 tails 数组
tails[left] = num;
// 如果插入位置是末尾,说明序列长度增加了
if (left == size) {
size++;
}
}
return size;
}
}
C# 实现
/**
* 300. 最长递增子序列
* 二分查找
*/
public class Solution
{
public int LengthOfLIS(int[] nums)
{
if (nums == null || nums.Length == 0)
{
return 0;
}
// tails[i] 表示长度为 i+1 的递增子序列的最小末尾元素
int[] tails = new int[nums.Length];
int size = 0;
foreach (int num in nums)
{
int left = 0, right = size;
while (left < right)
{
// 二分查找第一个 >= num 的位置
int mid = left + (right - left) / 2;
if (tails[mid] < num)
{
left = mid + 1;
}
else
{
right = mid;
}
}
// 更新 tails 数组
tails[left] = num;
// 如果插入位置是末尾,说明序列长度增加了
if (left == size)
{
size++;
}
}
return size;
}
}
复杂度分析
-
时间复杂度:O(n log n)
-
外层循环 O(n),每次循环内的二分查找 O(log n)
-
总体时间复杂度 O(n log n)
-
-
空间复杂度:O(n)
-
需要 tails 数组存储状态
-