Article / 文章
LeetCode 第300题:最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
📖 文章摘要
本文详细解析LeetCode第300题“最长递增子序列”,这是一道考察动态规划和二分查找的中等难度题目。文章提供了动态规划和贪心+二分查找两种实现方案,包含C#、Python、C++三种语言实现,配有详细的算法分析和性能对比。适合学习动态规划和二分查找的读者。
核心知识点: 动态规划、二分查找、贪心算法
难度等级: 中等
推荐人群: 具备基础算法知识,想要提升动态规划和二分查找能力的开发者
题目描述
给你一个整数数组 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
- -104 <= nums[i] <= 104
解题思路
本题可以使用两种方法来实现:
-
动态规划法:
- 定义dp[i]为以nums[i]结尾的最长递增子序列长度
- 对每个位置i,遍历前面的所有位置j
- 如果nums[i] > nums[j],则可以接在j后面
- 时间复杂度O(n²)
-
贪心+二分查找法:
- 维护一个递增子序列tails
- tails[i]表示长度为i+1的递增子序列的最小结尾值
- 对每个数字二分查找插入位置
- 时间复杂度O(nlogn)
图解思路
动态规划分析表
| 位置 | 当前数字 | dp值 | 更新过程 |
|---|---|---|---|
| 0 | 10 | 1 | 初始化为1 |
| 1 | 9 | 1 | 小于前值,独立为1 |
| 2 | 2 | 1 | 最小值,独立为1 |
| 3 | 5 | 2 | 可接在2后面 |
| 4 | 3 | 2 | 可接在2后面 |
| 5 | 7 | 3 | 可接在3或5后面 |
| 6 | 101 | 4 | 可接在7后面 |
| 7 | 18 | 4 | 可接在7后面 |
二分查找步骤表
| 步骤 | 操作 | 目的 |
|---|---|---|
| 1 | 初始化tails数组 | 存储各长度子序列的最小结尾 |
| 2 | 二分查找位置 | 找到当前数字的插入位置 |
| 3 | 更新tails | 维护最优解 |
| 4 | 返回长度 | 获取最终结果 |
代码实现
C# 实现
public class Solution {
// 动态规划解法
public int LengthOfLIS(int[] nums) {
if (nums == null || nums.Length == 0) return 0;
int[] dp = new int[nums.Length];
int maxLen = 1;
// 初始化dp数组
Array.Fill(dp, 1);
// 动态规划过程
for (int i = 1; i < nums.Length; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.Max(dp[i], dp[j] + 1);
}
}
maxLen = Math.Max(maxLen, dp[i]);
}
return maxLen;
}
// 贪心+二分查找解法
public int LengthOfLIS2(int[] nums) {
if (nums == null || nums.Length == 0) return 0;
int[] tails = new int[nums.Length];
int size = 0;
foreach (int num in nums) {
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[left] = num;
if (left == size) size++;
}
return size;
}
}
Python 实现
class Solution:
# 动态规划解法
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
dp = [1] * len(nums)
for i in range(1, len(nums)):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
# 贪心+二分查找解法
def lengthOfLIS2(self, nums: List[int]) -> int:
if not nums:
return 0
tails = []
for num in nums:
if not tails or num > tails[-1]:
tails.append(num)
else:
left, right = 0, len(tails)
while left < right:
mid = (left + right) // 2
if tails[mid] < num:
left = mid + 1
else:
right = mid
tails[left] = num
return len(tails)
C++ 实现
class Solution {
public:
// 动态规划解法
int lengthOfLIS(vector<int>& nums) {
if (nums.empty()) return 0;
vector<int> dp(nums.size(), 1);
int maxLen = 1;
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
maxLen = max(maxLen, dp[i]);
}
return maxLen;
}
// 贪心+二分查找解法
int lengthOfLIS2(vector<int>& nums) {
if (nums.empty()) return 0;
vector<int> tails;
for (int num : nums) {
if (tails.empty() || num > tails.back()) {
tails.push_back(num);
} else {
auto it = lower_bound(tails.begin(), tails.end(), num);
*it = num;
}
}
return tails.size();
}
};
执行结果
C# 实现
- 执行用时:92 ms (动态规划)
- 执行用时:88 ms (二分查找)
- 内存消耗:36.2 MB
Python 实现
- 执行用时:3644 ms (动态规划)
- 执行用时:76 ms (二分查找)
- 内存消耗:15.2 MB
C++ 实现
- 执行用时:276 ms (动态规划)
- 执行用时:8 ms (二分查找)
- 内存消耗:10.4 MB
性能对比
| 语言 | 方法 | 执行用时 | 内存消耗 | 特点 |
|---|---|---|---|---|
| C# | 动态规划 | 92 ms | 36.2 MB | 实现简单,性能一般 |
| C# | 二分查找 | 88 ms | 36.2 MB | 性能较好 |
| Python | 动态规划 | 3644 ms | 15.2 MB | 性能较差 |
| Python | 二分查找 | 76 ms | 15.2 MB | 性能提升明显 |
| C++ | 动态规划 | 276 ms | 10.4 MB | 性能中等 |
| C++ | 二分查找 | 8 ms | 10.4 MB | 性能最优 |
代码亮点
- 🎯 提供两种实现方案,适应不同场景
- 💡 使用二分查找优化时间复杂度
- 🔍 空间复杂度优化
- 🎨 代码结构清晰易懂
常见错误分析
- 🚫 忘记处理空数组情况
- 🚫 动态规划初始化错误
- 🚫 二分查找边界处理错误
- 🚫 更新最大长度时机错误
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 动态规划 | O(n²) | O(n) | 思路直观,易于理解 | 时间复杂度高 |
| 贪心+二分 | O(nlogn) | O(n) | 时间复杂度优,性能好 | 实现较复杂 |
相关题目
📖 系列导航
🔥 算法专题合集 - 查看完整合集
📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第300题。
💬 互动交流
感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。
如果这篇文章对你有帮助,请:
- 👍 点个赞,让更多人看到这篇文章
- 📁 收藏文章,方便后续查阅复习
- 🔔 关注作者,获取更多高质量算法题解
- 💭 评论区留言,分享你的解题思路或提出疑问
你的支持是我持续分享的动力!
💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!