紫书刷题进行中,题解系列点这里
习题3-9 UVA10340 All in All(13行AC代码)
思路分析
给定字符串s,t,判断s是否为t的子序列。子序列不同于子串
-
子串:必须相邻,比如
s=ab, t=cabafb
,t中第2,3位置为连续ab(下标从1开始),即子串s -
子序列:可不相邻,再如上例,下标为
[2,3]、[3,6]、[2,6]
表示的子序列均为s
双指针
本题仅为判定问题,最简单可直接使用双指针,但也有两种思路:
-
以s为参照:以i,j分别指向s,t开始,枚举s的每个元素ch,若在tj之后找到ch,i,j同时后移,否则说明不存在子序列
-
以t为参照(最简单):枚举t,以i指向s开始,若当前t元素ch==s[i],则共同后移,否则仅t后移
子序列dp
判定问题转为子序列计数问题,若个数为0,说明不存在,因此可用dp解决。具体思路如下:
使用map<char,vector<int>>mp
建立字符ch到ch在字符串s中出现位置集合的映射,此处使用set或vector均可,因此后期用滚动数组优化,需要逆序遍历出现位置,因此注意遍历顺序。
初始化dp[0]=1
,为第一个字符做准备,其余字符转移函数为dp[k]=dp[k]+(ch=='s[k]')*dp[k-1]
AC代码(C++11)
双指针(以s为参照,21行)
#include<bits/stdc++.h>
using namespace std;
string s, t;
int main() {
while(cin >>s && cin >>t) {
bool isSub = false; // 记录是否为子序列
int i = 0, j = 0; // 双指针
while (i < s.size() && j < t.size()) { // 以s为参照,遍历s,t
if (s[i] == t[j]) { // 找到相等
if (i == s.size() - 1) {
isSub = true;
break;
}
i ++; j ++;
}
while (j < t.size() && s[i] != t[j]) j ++;
}
printf("%s\n", isSub ? "Yes" : "No");
}
return 0;
}
双指针(以t为参照,13行)
#include<bits/stdc++.h>
using namespace std;
string s, t;
int main() {
while(cin >>s >>t) {
int i = 0;
for (auto ch : t) { // 以t为参照,依次比较
if (ch == s[i]) i ++;
}
printf("%s\n", (i == s.size()) ? "Yes" : "No");
}
return 0;
}
子序列dp计数(通用模板,25行)
#include<bits/stdc++.h>
using namespace std;
map<char, vector<int>> mp; // 存储每个字符所在位置
string s,t;
int main() {
while(cin>>s && cin >>t) {
mp.clear(); // 初始化
int dp[s.size()+1] = {1,0}; // dp计数
for (int i = 0; i < s.size(); i ++) { // 统计每个字符所在位置,从1开始算
if (mp.find(s[i]) == mp.end()) { // 未存在
mp.insert({s[i], {i+1}});
}
else mp[s[i]].push_back(i+1); // 已存在,存入
}
for (int i = 0; i < t.size(); i++) { // 遍历字符串t
if (mp.find(t[i]) != mp.end()) { // 字符出现在s中
for (int k = mp[t[i]].size() - 1; k >= 0; k --) { // 逆序遍历字符所在s中位置,滚动数组优化
dp[mp[t[i]][k]] += dp[mp[t[i]][k]-1]; // dp[j]=dp[j]+dp[j-1]
}
}
}
printf("%s\n", dp[s.size()] == 0 ? "No" : "Yes"); // 最后一个为总序列个数
}
return 0;
}
小结
虽然很简单的一道题,但从不同角度分析可得到许多有趣的结果。
以不同的字符串作为参照,代码量和思维量相差巨大(双指针,以s、t为参照)
将判定性问题转为计数问题,也是解决问题的一种思路(dp)