字符串知识(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] 的含义: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 的位置。用于非严格递增(允许相等)子序列。

相关推荐
zyl76543 分钟前
FlashAttention 详解:从 GPU 硬件、普通 Attention 到分块计算与 Online Softmax
算法
爱读书的小胖4 小时前
LeetCode 122:买卖股票的最佳时机 II(C)解题分析
算法
结局无敌5 小时前
React Native for OpenHarmony 实战:三方库 react-native-modal-dialog 的鸿蒙化适配指南
深度学习·算法·react native·react.js·harmonyos
WJ.Polar5 小时前
Java封装、继承、多态
java·开发语言
摇滚侠5 小时前
Java 中如何表示负数,有符号和无符号的区别,补码的计算规则,char byte short int long 的范围
java·开发语言
OPEN-F5 小时前
ROS2系列教程:上位机建图与导航(原理综合)
算法·机器人
weixin_307779135 小时前
C++代码实现MATLAB中的fitrtree函数功能
开发语言·c++·算法·matlab
AI wins5 小时前
c++新增頭文件
c++
Shadow(⊙o⊙)5 小时前
仿muduo实现高并发服务器——事件就绪管理Channel.hpp
服务器·c++
爱读书的小胖5 小时前
Codewars:Rot13(C)解题分析
c语言·开发语言