方法一:动态规划(基础解法)
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
if (n == 0) return 0;
vector<int> dp(n, 1); // dp[i] 表示以nums[i]结尾的LIS长度
int maxLen = 1;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (nums[j] < nums[i]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
maxLen = max(maxLen, dp[i]); // 更新全局最大值
}
return maxLen;
}
};
方法二:贪心 + 二分查找解法
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> tails; // tails[k] 表示长度为k+1的LIS的末尾最小元素
for (int num : nums) {
// 二分查找第一个 >= num 的位置
auto it = lower_bound(tails.begin(), tails.end(), num);
if (it == tails.end()) {
tails.push_back(num); // 可以扩展LIS长度
} else {
*it = num; // 替换为更小的末尾元素
}
}
return tails.size();
}
};
这段代码是贪心 + 二分查找解法的核心逻辑,用于维护一个数组 tails,其中 tails[k] 表示长度为 k+1 的递增子序列的末尾最小元素。以下是对 if (it == tails.end()) 条件的详细解释:
1. lower_bound 的作用
auto it = lower_bound(tails.begin(), tails.end(), num);
- 功能:在有序数组
tails中查找第一个大于等于num的元素位置。 - 返回值:
- 若存在这样的元素,返回其迭代器;
- 若不存在(即
num大于所有元素),返回tails.end()。
2. 条件判断逻辑
情况一:it == tails.end()
- 含义:
num大于tails中的所有元素。 - 操作:
tails.push_back(num)。 - 意义:
- 可以形成一个更长的递增子序列。例如:
- 若
tails = [2, 3, 7],当前num = 101,则101可接在7后面,形成长度为4的子序列[2, 3, 7, 101]。
- 若
tails的长度直接增加1,对应最长递增子序列的长度加1。
- 可以形成一个更长的递增子序列。例如:
情况二:it != tails.end()
- 含义:
tails中存在第一个大于等于num的元素,记为tails[i]。 - 操作:
*it = num(等价于tails[i] = num)。 - 意义:
- 用更小的
num替换tails[i],使得后续元素更容易扩展出更长的子序列。例如:- 若
tails = [2, 3, 7, 101],当前num = 18,则18 < 101,用18替换101后,tails变为[2, 3, 7, 18]。 - 这样,未来若有元素
20,可以接在18后面形成长度为4的子序列,而原来的101可能无法容纳更小的后续元素。
- 若
- 贪心策略:在保持子序列长度不变的前提下,尽可能让末尾元素更小,为后续扩展留有余地。
- 用更小的
3. 举例说明
以输入 nums = [2, 5, 3, 7, 101, 18] 为例,看 tails 的变化过程:
- 处理
2:tails为空,lower_bound返回end,tails.push_back(2)→tails = [2]。
- 处理
5:lower_bound找到2 < 5,返回end,tails.push_back(5)→tails = [2, 5]。
- 处理
3:lower_bound在[2,5]中查找第一个 ≥3 的元素,找到5(索引1)。- 用
3替换5→tails = [2, 3]。此时长度仍为2,但末尾元素更小。
- 处理
7:lower_bound返回end,tails.push_back(7)→tails = [2, 3, 7]。
- 处理
101:lower_bound返回end,tails.push_back(101)→tails = [2, 3, 7, 101]。
- 处理
18:lower_bound在[2,3,7,101]中查找第一个 ≥18 的元素,找到101(索引3)。- 用
18替换101→tails = [2, 3, 7, 18]。此时长度仍为4,但末尾元素更小,有利于后续扩展。
4. 为什么这样做能得到正确结果?
tails的性质:
tails始终是递增数组(因为每次添加元素都在末尾,替换操作也不会破坏递增顺序)。- 长度的正确性:
tails的长度始终等于当前最长递增子序列的长度。因为每次push_back都会增加长度,而替换操作不会改变长度。 - 末尾元素的最优性:
对于每个可能的长度k,tails[k-1]是所有长度为k的递增子序列中末尾最小的元素,这使得后续元素更容易形成更长的子序列。
总结
if (it == tails.end()):判断当前元素是否能扩展递增子序列的长度,能则添加到末尾。else:用当前元素替换tails中第一个大于等于它的元素,保证末尾元素尽可能小,为后续扩展留有余地。- 核心思想:通过贪心策略维护一个最小末尾元素的数组,结合二分查找实现O(nlogn)的高效解法。