习题3-9 UVA10340 All in All(13行AC代码)

紫书刷题进行中,题解系列点这里

习题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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值