
👨💻 关于作者:会编程的土豆
"不是因为看见希望才坚持,而是坚持了才看见希望。"
你好,我是会编程的土豆,一名热爱后端技术的Java学习者。
📚 正在更新中的专栏:
-
《数据结构与算法》😊😊😊
-
《leetcode hot 100》🥰🥰🥰🤩🤩🤩
-
《数据库mysql》
💕作者简介:后端学习者
标准 LCS 模版(二维 DP)
cpp
#include<iostream>
#include<vector>
#include<string>
using namespace std;
int LCS(string s1, string s2) {
int n = s1.size(), m = s2.size();
// dp[i][j] 表示 s1前i个字符 与 s2前j个字符 的LCS长度
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s1[i - 1] == s2[j - 1]) {
// 字符相同:从左上角+1
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// 字符不同:取左边或上边的最大值
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[n][m]; // 最终答案
}
理解不了可以强行记住;
dp[i][j]的含义:s1的前i个字符 与s2的前j个字符的最长公共子序列长度
情况 1:字符相同 s1[i-1] == s2[j-1]
-
说明当前字符可以加入公共子序列
-
长度 = 之前状态
dp[i-1][j-1]+ 1 -
对应在表格中就是:从左上角转移过来
情况 2:字符不同 s1[i-1] != s2[j-1]
-
当前字符不能同时使用
-
长度 = 从左边
dp[i][j-1]或上边dp[i-1][j]继承最大值 -
相当于跳过
s1的当前字符,或跳过s2的当前字符
举例演示
假设 s1 = "abcde",s2 = "ace"
DP 表格构建过程
| ∅ | a | c | e | |
|---|---|---|---|---|
| ∅ | 0 | 0 | 0 | 0 |
| a | 0 | 1 | 1 | 1 |
| b | 0 | 1 | 1 | 1 |
| c | 0 | 1 | 2 | 2 |
| d | 0 | 1 | 2 | 2 |
| e | 0 | 1 | 2 | 3 |
例如:

cpp
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
string s; cin >> s;
int n = s.size();
string t = s;
reverse(s.begin(), s.end());
vector<vector<int>>dp(n + 1, vector<int>(n+1,0));
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (s[i - 1] == t[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
cout << n - dp[n][n] << endl;
return 0;
}
LIS(最长上升子序列)标准模版
cpp
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
if (n == 0) return 0;
// tails[k] 表示长度为 k+1 的上升子序列的最小末尾元素
vector<int> tails;
for (int x : nums) {
// 在 tails 中找第一个 >= x 的位置
auto it = lower_bound(tails.begin(), tails.end(), x);
if (it == tails.end()) {
// x 比所有末尾都大,可以延长最长子序列
tails.push_back(x);
} else {
// 替换掉第一个 >= x 的元素,保持末尾尽可能小
*it = x;
}
}
return tails.size();
}
两种情况
| 情况 | 操作 | 含义 |
|---|---|---|
it == tails.end() |
tails.push_back(x) |
x 比所有现有末尾都大,可以延长最长上升子序列 |
it != tails.end() |
*it = x |
找到一个比 x 大的末尾,用 x 替换它,让该长度的末尾更小 |
变种:输出具体序列
cpp
vector<int> getLIS(vector<int>& nums) {
int n = nums.size();
vector<int> tails; // 记录最小末尾
vector<int> pos(n); // pos[i] 记录 nums[i] 在 tails 中的位置
vector<int> prev(n, -1); // prev[i] 记录 nums[i] 的前驱索引
for (int i = 0; i < n; i++) {
auto it = lower_bound(tails.begin(), tails.end(), nums[i]);
int idx = it - tails.begin();
if (it == tails.end()) {
tails.push_back(nums[i]);
} else {
*it = nums[i];
}
pos[i] = idx;
if (idx > 0) {
// 找前一个位置的元素(倒序遍历找 pos[j] == idx-1 的第一个)
for (int j = i - 1; j >= 0; j--) {
if (pos[j] == idx - 1 && nums[j] < nums[i]) {
prev[i] = j;
break;
}
}
}
}
// 回溯构造 LIS
vector<int> result;
int len = tails.size();
for (int i = n - 1; i >= 0; i--) {
if (pos[i] == len - 1) {
int cur = i;
while (cur != -1) {
result.push_back(nums[cur]);
cur = prev[cur];
}
reverse(result.begin(), result.end());
break;
}
}
return result;
}
例如

cpp
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
vector<int>arr;
int x;
while (cin >> x)
{
arr.push_back(x);
}
reverse(arr.begin(), arr.end());
vector<int>tail;
for (int i = 0; i < arr.size(); i++)
{
int x = arr[i];
auto it = upper_bound(tail.begin(),tail.end(),x);
if (it == tail.end())
{
tail.push_back(x);
}
else *it = x;
}
cout << tail.size() << endl;
vector<int>taila;
reverse(arr.begin(), arr.end());
for (int i = 0; i < arr.size(); i++)
{
int x = arr[i];
auto it = lower_bound(taila.begin(), taila.end(), x);
if (it == taila.end())
{
taila.push_back(x);
}
else *it = x;
}
cout << taila.size() << endl;
return 0;
}
-
lower_bound:找第一个>= x的位置。用于严格递增子序列。 -
upper_bound:找第一个> x的位置。用于非严格递增(允许相等)子序列。