0291. 单词规律 II
大约 3 分钟
---
0291. 单词规律 II
- 标签:哈希表、字符串、回溯
- 难度:中等
题目链接
题目大意
描述:
给定一种规律 和一个字符串 。
要求:
判断 是否和 的规律相匹配。
如果存在单个字符到「非空」字符串的「双射映射」,那么字符串 匹配 ,即:如果 中的每个字符都被它映射到的字符串替换,那么最终的字符串则为 。「双射」意味着映射双方一一对应,不会存在两个字符映射到同一个字符串,也不会存在一个字符分别映射到两个不同的字符串。
说明:
- 。
- 和 由小写英文字母组成。
示例:
- 示例 1:
输入:pattern = "abab", s = "redblueredblue"
输出:true
解释:一种可能的映射如下:
'a' -> "red"
'b' -> "blue"
- 示例 2:
输入:pattern = "aaaa", s = "asdasdasdasd"
输出:true
解释:一种可能的映射如下:
'a' -> "asd"
解题思路
思路 1:回溯 + 双射映射
核心在于构造规律字符到字符串子串的双射映射。设规律串为 ,目标串为 。在回溯过程中维护两张映射表:
- :从规律字符到非空子串的映射;
- :从非空子串到规律字符的反向映射;
用深度优先搜索 dfs(i, j)
表示当前匹配到 的下标 与 的下标 :
- 终止条件:如果 且 ,说明恰好匹配成功,返回 True;如果其中一个到达末尾另一个未到达,返回 False。
- 设当前规律字符为 。
- 如果 已在 中映射到某个子串 ,则检查 在位置 是否以 为前缀:即 。如果匹配则递归到 ,否则返回 False。
- 如果 尚未建立映射,则需要在 的后缀中尝试所有可能的非空切分 (其中 )。如果该子串尚未被其他字符使用(即不在 中),则建立双射 、,并递归 。如果递归失败,撤销映射继续尝试下一个切分。
- 简单剪枝:剩余字符至少要占用长度 1,如果 ,可直接返回 False。
思路 1:代码
class Solution:
def wordPatternMatch(self, pattern: str, s: str) -> bool:
# 回溯 + 双射映射
# p2w: 规律字符 -> 具体子串;w2p: 子串 -> 规律字符
p2w = {}
w2p = {}
def dfs(i: int, j: int) -> bool:
# 如果同时走到末尾,匹配成功
if i == len(pattern) and j == len(s):
return True
# 只有一个到末尾则失败
if i == len(pattern) or j == len(s):
return False
# 剪枝:剩余 pattern 字符至少需要占用剩余 s 的长度
if len(s) - j < len(pattern) - i:
return False
c = pattern[i]
# 如果已有映射,校验前缀
if c in p2w:
t = p2w[c]
# s 当前前缀不匹配则失败
if not s.startswith(t, j):
return False
return dfs(i + 1, j + len(t))
# 否则尝试所有可能的非空切分 s[j:k]
for k in range(j + 1, len(s) + 1):
t = s[j:k]
# 子串已被其他字符占用则跳过,保证双射
if t in w2p:
continue
# 建立双向映射
p2w[c] = t
w2p[t] = c
if dfs(i + 1, k):
return True
# 回溯撤销
del p2w[c]
del w2p[t]
return False
return dfs(0, 0)
思路 1:复杂度分析
- 时间复杂度:最坏情况下为指数级。每个未映射的规律字符 可能尝试 个切分,整体可上界为 。由于题目规模较小(均不超过 20),回溯可接受。
- 空间复杂度:。主要为递归栈与映射表存储,映射的子串总长度不超过 。