字符串知识(LCS,LIS)区分总结归纳

👨‍💻 关于作者:会编程的土豆

"不是因为看见希望才坚持,而是坚持了才看见希望。"

你好,我是会编程的土豆,一名热爱后端技术的Java学习者。

📚 正在更新中的专栏:

💕作者简介:后端学习者

标准 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] 的含义:s1i 个字符s2j 个字符的最长公共子序列长度

情况 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 的位置。用于非严格递增(允许相等)子序列。

相关推荐
FuckPatience2 小时前
未能加载项目文件。名称不能以“<”字符(十六进制值 0x3C)开头
开发语言
书到用时方恨少!2 小时前
Python 面向对象编程:从“过程清单”到“智能积木”的思维革命
开发语言·python·面向对象
北顾笙9802 小时前
day25-数据结构力扣
数据结构·算法·leetcode
冰暮流星2 小时前
javascript案例-简易计算器
开发语言·javascript·ecmascript
Rsun045512 小时前
5、Java 原型模式从入门到实战
java·开发语言·原型模式
lxh01132 小时前
最接近的三数之和
java·数据结构·算法
天若有情6732 小时前
原创C++设计模式:功能归一化——无继承、轻量版AOP,比传统OOP更优雅
开发语言·c++·设计模式·oop
FrontAI2 小时前
Next.js从入门到实战保姆级教程:实战项目(上)——全栈博客系统架构与核心功能
开发语言·前端·javascript·react.js·系统架构
zhangzeyuaaa2 小时前
深入 Python 模块与包:从自定义到标准库,再到第三方库的完全指南
开发语言·python