1.动态规划(O(n^2))
动态规划是求解 LIS 问题的经典方法。它通过构建一个数组 dp,dp[i] 表示以 a[i] 结尾的最长上升子序列的长度。
实现思路:
- 初始化
dp数组,dp[i]初始化为1,表示每个元素自身都可以作为一个长度为1的子序列。 - 对每个元素
a[i],遍历其之前的所有元素a[j],如果a[i] > a[j],则dp[i] = max(dp[i], dp[j] + 1)。 - 最后返回
dp数组中的最大值。int lengthOfLIS(vector<int>& nums) { int n = nums.size(); if (n == 0) return 0; vector<int> dp(n, 1); int maxLen = 1; for (int i = 1; i < n; 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; }
2.动态规划 + 二分查找(O(n log n))
为了优化 O(n^2) 的动态规划方法,可以结合二分查找来提高效率。这种方法利用一个辅助数组 dp,该数组并不代表实际的 LIS,而是用于维护可能的 LIS 结尾元素的最小值。
实现步骤:
- 初始化一个空的
dp数组。 - 对于每个元素
a[i],使用lower_bound在dp中找到第一个不小于a[i]的位置it。- 如果
it是dp.end(),则将a[i]添加到dp的末尾。 - 否则,用
a[i]替换dp[it]。
- 如果
最终,dp 数组的长度即为 LIS 的长度。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int LIS(vector<int>& a) {
vector<int> dp; //类似于栈
for (int i = 0; i < a.size(); i++) {
// 在dp中找到第一个不小于 a[i] 的元素
auto it = lower_bound(dp.begin(), dp.end(), a[i]);
// 如果没有找到,则将 a[i] 压入栈
if (it == dp.end()) {
dp.push_back(a[i]);
} else {
// 如果找到了,则替换掉那个元素
*it = a[i];
}
}
return dp.size();
}
int main() {
int n; cin >> n;
vector<int> a(n);
for(int i = 0; i < n; i++) cin >> a[i];
cout << LIS(a) << endl;
return 0;
}
3.分治法
分治法是一种常见的算法策略,特别适用于有特定要求或结构的问题。在求解最长上升子序列(LIS)的问题时,分治法通常会将问题分成左右两部分,然后分别求解每一部分的 LIS,最后将结果合并。这种方法特别适用于有一些特殊约束的情况,比如必须包含某个特定元素(例如中间元素)。
1. 确定分治的分界点
首先,确定数组的中间位置 mid。对于数组长度为 n 的数组:
int mid = (n - 1 ) / 2 // 不分奇偶
这个中间位置的元素通常是一个需要保留的元素,它在求解 LIS 的过程中起到了连接左右部分的关键作用。
2. 构造 b 数组
接下来,通过筛选 a 数组的元素,构造一个新的数组 b,以便简化后续的 LIS 计算:
- 对于
mid左侧的元素,保留所有小于a[mid]的元素。 - 保留
mid位置的元素a[mid]本身。 - 对于
mid右侧的元素,保留所有大于a[mid]的元素。
通过这种筛选,b 数组包含了那些可能对 LIS 有贡献的元素,排除了不必要的元素,简化了问题的规模。
3. 在 b 数组上计算 LIS
在构造完 b 数组后,使用标准的 LIS 求解方法(通常是动态规划结合二分查找的 O(n log n) 方法)来计算 b 数组的 LIS。具体步骤如下:
- 初始化一个空的动态数组
dp,用于存储 LIS 的候选结尾元素。 - 遍历
b数组,对于每个元素b[i],使用lower_bound函数查找dp中第一个不小于b[i]的位置:- 如果
b[i]比dp中的所有元素都大,则将其添加到dp的末尾。 - 否则,替换
dp中对应位置的元素,以保持dp的单调递增性。
- 如果
最终,dp 数组的长度即为 b 数组的 LIS 长度。
4. 计算需要删除的元素数量
最后,通过 n - dp.size() 计算需要删除的元素数量,即原数组长度减去 LIS 的长度。这个值代表了为了使剩下的元素构成一个包含 a[mid] 的最长上升子序列,所需要删除的元素的最小数量。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 20010;
int a[N],b[N],dp[N];
int main() {
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t; cin >> t;
while(t --){
int n; cin >> n;
int mid = (n - 1) / 2;
for(int i = 0; i < n; i++) cin >> a[i];
int cnt = 0;
// 构造b数组维护单调序列
for(int i = 0; i < n; i ++){
if(i < mid && a[i] < a[mid]) b[cnt ++] = a[i];
else if( i == mid) b[cnt ++] = a[i];
else if(i > mid && a[i] > a[mid]) b[cnt ++] = a[i];
}
int len = 0;
for(int i = 0; i < cnt; i++){
// 返回第一个大于等于b[i]的数
int indix = lower_bound(dp,dp + len + 1,b[i]) - dp;
dp[indix] = b[i];
len = max(len,indix);
}
cout <<n - len << '\n' ;
}
return 0;
}
1944

被折叠的 条评论
为什么被折叠?



