#LC290. 单词规律
单词规律
你好,各位竞赛同学!今天我们来学习一道字符串处理与双向映射(双射)判定的基础练习题。在 NOI 竞赛中,字符串模拟与哈希表的灵活运用是必须掌握的基本功。
我们今天要讨论的题目是:Word Pattern (单词规律)。
一、 题目描述 (NOI 竞赛风格)
题目名称:Word Pattern 时间限制:1.0s 内存限制:128MB
【问题描述】
给定一组规律 pattern 和一个字符串 s,请判断 s 是否遵循相同的规律。
这里的“遵循”是指完全匹配,即在规律 pattern 中的每个字母和字符串 s 中的每个非空单词之间存在着**双射(Bijection)**关系。
pattern中的每个字符必须恰好对应s中的一个单词。s中的每个单词必须恰好对应pattern中的一个字符。- 这种映射关系必须是一一对应的,且不可改变。
【输入格式】
输入共两行。
第一行包含一个字符串 pattern,仅包含小写英文字母。
第二行包含一个字符串 s,包含小写英文字母和空格。单词之间由单个空格分隔,输入不含前导或尾随空格。
【输出格式】
如果符合规律,输出 true,否则输出 false。
【样例输入 #1】
abba
dog cat cat dog
【样例输出 #1】
true
【样例输入 #2】
abba
dog cat cat fish
【样例输出 #2】
false
【数据范围】
s只包含小写英文字母和空格' '。
二、 预备知识点
- 字符串拆分 (Tokenization):如何高效地从长字符串中提取被空格分隔的单词。
- 哈希表 (Hash Map):建立
char到string以及string到char的双向映射。 - 双射关系 (Bijection):理解 A 映射到 B 的同时,B 也必须唯一映射到 A。
三、 启发式推理过程(草稿纸上的步骤)
请大家拿出草稿纸,我们模拟一遍思维过程:
1. 具象化拆解
面对输入 abba 和 dog cat cat dog,我们第一步要做什么?
- 动作:把
s切开。 - 结果:我们得到一个单词序列:
["dog", "cat", "cat", "dog"]。 - 检查:规律有 4 个字母,单词有 4 个。如果数量不等(例如规律 4 个,单词 5 个),直接判定失败。
2. 建立档案(双向核对)
我们需要保证“双向奔赴”。
- 尝试单向映射的风险:如果
a -> dog,b -> dog。从规律看没问题,但从单词看,dog对应了两个不同的规律字符,这违反了双射原则。
3. 模拟匹配(草稿记录)
以规律 abba 为例:
- 读取
a和dog:- 查档案:
a有人领了吗?没有。dog有人领了吗?没有。 - 记录:
a <-> dog。
- 查档案:
- 读取
b和cat:- 查档案:
b和cat都是新人。 - 记录:
b <-> cat。
- 查档案:
- 读取
b和cat:- 查档案:
b已经心有所属(cat)。核对当前单词:是cat,一致,通过。
- 查档案:
- 读取
a和dog:- 查档案:
a已经心有所属(dog)。核对当前单词:是dog,一致,通过。
- 查档案:
结论:全部通过,结果为 true。
四、 逻辑流程图 (Mermaid 语法避障版)
graph TD
Start(开始) --> Input(读取 pattern 和字符串 s)
Input --> Split(拆分 s 为单词列表 words)
Split --> CountCheck{pattern 长度是否等于 words 长度}
CountCheck -- 否 --> ReturnFalse(返回 false)
CountCheck -- 是 --> LoopStart(遍历索引 i 从 0 到 N 减 1)
LoopStart --> GetVar(取出字符 p_i 和单词 w_i)
GetVar --> MapCheck1{p_i 已经在映射中}
MapCheck1 -- 是 --> ValCheck1{映射值是否等于 w_i}
ValCheck1 -- 否 --> ReturnFalse
ValCheck1 -- 是 --> MapCheck2
MapCheck1 -- 否 --> MapCheck2{w_i 已经在反向映射中}
MapCheck2 -- 是 --> ValCheck2{反向映射值是否等于 p_i}
ValCheck2 -- 否 --> ReturnFalse
ValCheck2 -- 是 --> Next
MapCheck2 -- 否 --> Record(在两个映射表中记录 p_i 与 w_i 的关系)
Record --> Next(进入下一轮循环)
Next --> LoopEnd{循环是否结束}
LoopEnd -- 否 --> LoopStart
LoopEnd -- 是 --> ReturnTrue(返回 true)
ReturnFalse --> End(结束)
ReturnTrue --> End
五、 时间与空间复杂度分析
-
时间复杂度:
- 拆分字符串:需要遍历一次
s,复杂度 , 是s的长度。 - 哈希表操作:遍历
pattern长度 次。每次插入和查找字符串哈希表的复杂度取决于单词平均长度 。 - 总计:。由于 的量级大约等于 ,故总体为 。在 的规模下,运行时间远低于 1s。
- 拆分字符串:需要遍历一次
-
空间复杂度:
- 需要存储单词列表和两个哈希表。
- 总计:,主要取决于字符串存储空间。
优化建议:
- 在 C++ 中,使用
std::stringstream可以方便地拆分单词,但在超大规模数据下,手动双指针遍历s寻找空格会更快。 - 使用
std::unordered_map替代std::map可以获得更好的平均查询常数。
六、 读题关键词提取(避坑指南)
在处理字符串匹配类题目时,请圈出以下关键词:
- “一一对应” / “双射” (Bijection):这意味着你必须检查两个方向的唯一性(字符到单词,单词到字符)。
- “非空单词”:注意处理空格,确保拆分后的内容有效。
- “规律长度与字符串规模”:如果长度不等,哈希表逻辑之前就要特判。
- “字符集范围”:本题仅小写字母,这意味着字符映射可以用
string map[26]数组来优化常数。
教练提示:在 NOI 比赛中,字符串处理题往往隐藏着特殊情况(如 s 比 pattern 长很多或短很多)。先写长度判断,再写逻辑,能帮你规避 50% 的低级错误。加油!