每日一题
面试题:最长递增子序列(Longest Increasing Subsequence, LIS)
题目描述
给定一个整数数组 nums
,找到其中最长严格递增子序列的长度(子序列不要求连续)。
示例:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,5,7,101]
,长度为 4。
答案与分析
1. 动态规划解法(时间复杂度 O(n²))
思路
- 定义
dp[i]
表示以nums[i]
结尾的最长递增子序列的长度。 - 对每个
i
,遍历j
从0
到i-1
,若nums[i] > nums[j]
,则dp[i] = max(dp[i], dp[j]+1)
。
代码实现
python
复制
def length_of_LIS(nums):dp = [1] * len(nums)for i in range(len(nums)):for j in range(i):if nums[i] > nums[j]:dp[i] = max(dp[i], dp[j] + 1)return max(dp) if nums else 0
分析
- 时间复杂度为 O(n²),空间复杂度 O(n)。
- 优点:思路直观,易于理解。
- 缺点:当
n
较大时(如n=1e4
),性能较差。
2. 贪心 + 二分查找优化(时间复杂度 O(n log n))
思路
- 维护一个数组
tails
,其中tails[i]
表示长度为i+1
的递增子序列的最小末尾值。 - 遍历数组,对每个数进行二分查找,找到其在
tails
中的位置并更新或插入。
代码实现
python
复制
def length_of_LIS(nums):tails = []for num in nums:left, right = 0, len(tails)while left < right:mid = (left + right) // 2if tails[mid] < num:left = mid + 1else:right = midif left == len(tails):tails.append(num)else:tails[left] = numreturn len(tails)
分析
- 时间复杂度为 O(n log n),空间复杂度 O(n)。
- 核心思想:通过维护
tails
数组,保证其严格递增。每次插入或替换时,使用二分查找优化效率。 - 为什么能保证正确性?
- 若当前数比
tails
所有元素大,说明可以形成更长的子序列。 - 若当前数能替换
tails
中某个元素,说明可以优化后续子序列的构造(更小的末尾值更有利于后续扩展)。
- 若当前数比
面试考察点
- 动态规划基础:是否能设计状态转移方程。
- 算法优化能力:是否能想到贪心策略和二分查找的结合。
- 代码实现细节:二分查找的边界条件处理、数组更新逻辑。
- 复杂度分析:对时间/空间复杂度的理解和权衡能力。