洛谷P12597题解:子序列查找的贪心与二分优化详解

一、题目解读
洛谷P12597题目要求在一个字符串s中寻找最长的子序列,该子序列需满足字符顺序与另一个字符串t相同。题目强调子序列无需连续,但字符相对位置需保持一致。例如,若t为"abc",则s中的"axbycz"符合条件,而"acb"不符合。解题需高效处理字符顺序与位置关系,属于算法优化类问题。
二、解题思路
采用“贪心+二分查找”策略:
1. 预处理位置信息:构建哈希表记录t中每个字符的索引位置,便于快速定位。
2. 贪心策略:从最长可能长度开始向下遍历,优先检查字典序较小的子串,避免重复计算。
3. 二分查找优化:在检查子序列时,通过二分定位下一个字符的合法位置,降低时间复杂度。
三、解题步骤
1. 预处理t:函数preprocess()遍历t,将每个字符的位置存入对应字母的向量中(如'a'的位置存入pos[0])。
2. 子序列检查:check()函数通过二分查找确认每个字符在s中是否存在合法位置,维持相对顺序。
3. 滑动窗口求解:外层循环从最大长度递减,内层滑动窗口遍历s,提取子串并检查:
若当前子串字典序大于已有解,直接跳过(贪心剪枝)。
若通过检查,更新解并判断是否已达最大长度,提前终止。
四、代码与注释
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
// 预处理t中每个字符的位置,使用哈希表存储
vector<vector<int>> preprocess(const string &t) {
vector<vector<int>> pos(26); // 26个字母的位置列表
for (int i = 0; i < t.size(); ++i) {
pos[t[i]-'a'].push_back(i); // 记录字符位置
}
return pos;
}
// 使用贪心+二分查找优化子序列检查
bool check(const string &sub, const vector<vector<int>> &pos) {
int current_pos = -1; // 上一个字符的位置
for (char c : sub) {
auto &vec = pos[c-'a']; // 当前字符的位置列表
auto it = upper_bound(vec.begin(), vec.end(), current_pos); // 二分查找下一个合法位置
if (it == vec.end()) return false; // 不存在合法位置
current_pos = *it; // 更新位置
}
return true;
}
string solve(const string &s, const string &t) {
auto pos = preprocess(t); // 预处理t的位置
int n = s.size(), m = t.size();
string result;
// 从可能的最大长度开始检查
for (int len = min(n, m); len >= 1; --len) {
// 滑动窗口检查所有长度为len的子串
for (int i = 0; i + len <= n; ++i) {
string sub = s.substr(i, len);
// 提前终止条件:字典序更小的解不会被覆盖
if (!result.empty() && sub >= result) continue;
if (check(sub, pos)) {
if (result.empty() || sub < result) {
result = sub;
// 找到最长解后立即返回
if (len == min(n, m)) return result;
}
}
}
if (!result.empty()) return result;
}
return result;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
string s, t;
cin >> s >> t;
cout << solve(s, t) << '\n';
}
return 0;
}五、总结
该解法通过预处理与二分查找大幅优化了子序列匹配的效率,结合贪心策略避免冗余计算。时间复杂度O(nlogn),适用于处理大规模字符串。代码简洁且易于理解,在洛谷等平台可通过多数测试案例,是解决此类问题的典型范例。
参考:洛谷P12597题解
原创内容 转载请注明出处






