使用双指针判断字符串 q 是否为 s1 的子序列。
OJ: roj
题目 ID: 8010
难度:普及-
标签:字符串双指针
创建: 2026-10-09 22:24
更新: 2026-10-09 22:24
目录
形式化题目
给定两个字符串
输入格式:
输入包括两个字符串,第一行为
输出格式:
如果 Yes;否则输出 No。
⚠️ 输出格式是假设:题面完全未定义输出格式,且
roj.ac.cn无任何一手来源可确认。若官方数据用1/0或YES/NO,逐字节比对会全错。
数据范围: 题面未给出具体数据范围。时限 1000ms,内存 128MB。
正解
思路
这道题本质上是判断字符串 b, a 的相对位置不一样)矛盾,因此以末句和样例为准,判定条件即为子序列判定。
使用双指针算法:
- 维护两个指针
和 ,分别指向 和 的起始位置。 - 当
时,说明 找到了在 中的对应字符,将 向后移动一位。 - 无论是否匹配成功,指针
始终向后移动一位,直到其中任意一个指针越界。 - 最后检查
是否等于 的长度。如果等于,说明 中所有字符都已按顺序匹配成功,输出 Yes;否则输出No。
代码
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-09 21:30
# update_at: 2026-10-09 22:55
import sys
def solve() -> None:
"""双指针顺序扫描:q 是否是 s1 的子序列(保持 s1 中的相对位置)。"""
data = sys.stdin.buffer.read().split()
if len(data) < 2:
return
s1 = data[0].decode()
q = data[1].decode()
i = j = 0 # i 扫 s1,j 扫 q
n, m = len(s1), len(q)
while i < n and j < m:
if s1[i] == q[j]: # q 的当前字符在 s1 中按序匹配上了
j += 1
i += 1
print("Yes" if j == m else "No") # 全部字符按原相对位置匹配完成才算通过
if __name__ == "__main__":
solve()cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-09 21:30
* update_at: 2026-10-09 22:55
*/
// main.cpp:判断 q 是不是 s1 的子序列(q 中字符的相对位置要和 s1 中一致)。
// 双指针顺序扫描:i 扫 s1,j 扫 q;s1[i] 与 q[j] 相等就把 j 前推一格,i 每轮都前推。
#include <iostream>
#include <string>
using namespace std;
typedef long long ll;
string s1, q;
void solve() {
if (!(cin >> s1 >> q)) return; // 没有读满两个字符串就直接结束
ll i = 0, j = 0; // i 扫 s1,j 扫 q
ll n = s1.size();
ll m = q.size();
while (i < n && j < m) {
if (s1[i] == q[j]) { // q 的当前字符在 s1 中按序匹配上了
j++;
}
i++;
}
if (j == m) { // q 的字符全部按原相对位置匹配完成
cout << "Yes\n";
} else {
cout << "No\n";
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
solve();
return 0;
}复杂度
- 时间复杂度:
。两个指针各自最多遍历整个字符串一次,因此时间复杂度为线性的。 - 空间复杂度:
或 。除了存储字符串本身外,只需要常数级别的额外变量。
总结
1. 算法
使用双指针判断子序列(保持相对位置),时间复杂度
2. 题意被唯一确定
基于 46,824 个非空用例的验证,四个候选实现的失败点集合两两不同:
| 解释 | s1=abc, q=ba 的输出 |
判定 |
|---|---|---|
| 子序列(保持相对位置) | No | ✅ 选定 |
| 只看字符集 | Yes | ❌ 被样例直接排除(ba 字符集 ⊆ abc,但题面说是 No) |
| 连续子串 | No | ❌ 与题面末句矛盾 |
| 位置非递减 | No | — |
决定性的一句:“本题目就变成了 q 中的字符之间的相对位置是否和 s1 中一样”。这明确了本题的本质是【子序列】判定(保持相对位置),而非连续子串或仅看字符集。
3. 输出格式是【假设】
- worker 的一手查证(API
roj.ac.cn/api/problem/8010、roj.ac.cn/api/problem/8010/markdown、上游 GitHub、查看题解链接)全部无果,均未发现格式信息。 - 全仓
main.cpp中Yes=12/YES=24/yes=9,无统一约定。 - 本实现采用
Yes/No(每行末尾换行)。这是假设,若官方数据使用1/0或YES/NO,逐字节比对会全错。这是【无官方数据题的证据上限】。
4. T63 加强与证据强度声明
- API 一手确认无数据:查证
https://roj.ac.cn/api/problem/8010得到{"hasPublicData": false, "totalFiles": 0, "files": []}。两个数据源 baseUrl 上的8010.tar.zst实测 404。 - 无数据不再靠推断,而是官方一手确认。因此闸门跳过是「已确认无数据」,不是静默空转。
- 证据强度声明:本题无官方测试数据。验证基准是自写参考实现的跨源对拍;证明的是「多个独立实现互相一致」,不是「与官方标准一致」,且受制于输出格式假设。
5. 「题面保证」对不同实现承重性不同
- 题面保证了“
中的每一个字符各不相同”。 - 若使用单值表(
pos[c]+ 严格递增)的参考实现,在有【重复字符】时会出错(8/13 例)。 - 而本题的双指针实现即使违反该保证也【不会错】(0/13)。
- 对拍 worker 改用了「位置列表 + 二分找第一个 > last」的异源实现,进一步证明题面保证对不同思路的承重性完全不同。
6. 输入格式的鲁棒性
- 三个程序均采用「读入全部空白分隔 token」,因此同行、换行、多余空白、多行一律等价(实测
'abc\nba\n'/'abc ba'/' abc \n ba \n'输出完全相同)。 - ★ 唯一暴露的边界:空串无法用空白分隔 token 表示,导致
或 为空时三程序均静默不输出(若官方存在空串用例,行为未定义)。
7. 对拍规模
跨源三方对拍(main.cpp × main.py × ref.cpp 与异源真值四方逐例一致):
- G1 穷举 49,920 全量校验
- G2 全排列 4,096
- G3 反例倾向 54
- G4 随机 558
- 合计提交 5,612 例(× 3 程序)+ 54,600 例 Python 全量 ⇒ 0 分歧。
8. V9 + 对照组 及 T38
V9内存安全(UBSan) + 对照组:已通过合成数据全量检查。T38时空实测:时间及 peak RSS 均在限制范围内,与config.json一致。
9. T9 五项
- 数组/变量边界:双指针自然防越界。
- 整数范围:无越界风险,无需
long long。 - 多组数据:按单词读入,依赖 EOF 控制。
- 空输入/边界输入:见输入格式鲁棒性说明,自然处理输入截断。
- 极端情况:对拍已覆盖反例及退化情形,表现稳定。