文章目录
- 摘要
- 描述
- 题解答案
- 题解代码分析
- 示例测试及结果
- 时间复杂度
- 空间复杂度
- 总结
摘要
在开发中我们经常遇到“模式匹配”的问题,比如日志分类、用户意图识别、甚至是在一些权限系统中做规则映射判断。这类问题的本质是判断两个结构是否具有一致的对应关系。LeetCode 290 题“单词规律”就是这样的一个小而经典的场景。
本文将用 Swift 完整讲解该问题的解法,包含实际 Demo、代码分析,并用口语化方式带你理解每一处逻辑。
描述
我们有一个字符串 pattern
,比如 "abba"
,还有一个字符串 s
,比如 "dog cat cat dog"
。我们要判断:这个字符串 s
是不是按照 pattern 中的规律来组织的。
规律是什么意思?就是 pattern
中的每个字符都应该“代表”字符串中的一个具体单词,而且这个关系必须是双向绑定的——不能两个字母指向同一个单词,也不能一个字母指向两个不同的单词。
举个例子:
- pattern =
"abba"
- s =
"dog cat cat dog"
意思就是:
- 第一个
a
对应dog
- 第一个
b
对应cat
- 第二个
b
还是cat
- 第二个
a
还是dog
前后一致,双向对应 —— 合法。
但如果是:
- pattern =
"abba"
- s =
"dog cat cat fish"
这就不合法了,因为最后的 a
对应的是 fish
,而最开始是 dog
,对应关系对不上。
题解答案
我们要做的就是:
- 拆开
s
,按空格分割成单词数组。 - 遍历 pattern 和单词数组,分别记录 pattern 字符 -> 单词,和 单词 -> pattern 字符 的映射关系。
- 每一步都去检查是否一致。只要发现有一个地方对不上,就返回 false。
- 如果都对上了,就返回 true。
题解代码分析
下面是完整的 Swift 代码,包含注释,适合直接运行:
import Foundation
func wordPattern(_ pattern: String, _ s: String) -> Bool {
let words = s.split(separator: " ").map { String($0) }
let chars = Array(pattern)
// 如果数量不一致,肯定不符合规律
guard words.count == chars.count else {
return false
}
// 字母 -> 单词 的映射
var charToWord = [Character: String]()
// 单词 -> 字母 的映射
var wordToChar = [String: Character]()
for (c, w) in zip(chars, words) {
if let mappedWord = charToWord[c] {
if mappedWord != w {
return false
}
} else {
charToWord[c] = w
}
if let mappedChar = wordToChar[w] {
if mappedChar != c {
return false
}
} else {
wordToChar[w] = c
}
}
return true
}
示例测试及结果
我们来跑几个例子看看:
print(wordPattern("abba", "dog cat cat dog")) // true
print(wordPattern("abba", "dog cat cat fish")) // false
print(wordPattern("aaaa", "dog cat cat dog")) // false
print(wordPattern("abc", "one two three")) // true
print(wordPattern("abc", "one one one")) // false
结果输出:
true
false
false
true
false
每一个测试例子都能直观说明题目的要求。
时间复杂度
这个算法是 O(n) 级别的,n 是 pattern
的长度(也就是 s
中单词的数量)。因为我们是线性遍历了一次 pattern
和 s
,并做了常数级别的字典操作。
空间复杂度
空间上我们用了两个 HashMap(也就是 Swift 的 Dictionary
),分别记录 char -> word
和 word -> char
,最坏情况下就是每个字符和单词都不一样,那就是 O(n) 空间。
总结
“单词规律”这个问题看起来像字符串处理,其实本质是双射关系映射 —— 字母到单词,单词到字母都要唯一对应。日常开发中,如果你在做“模板渲染”、“日志模式分类”、“命名规范检查”等功能时,都可能会遇到类似的需求。
这类题的关键点是:
- 把 pattern 和 s 拆开并配对
- 同时维护两个方向的映射
- 在每一对中,及时做一致性校验
用 Swift 解题时,字典的高效查找和 zip()
的组合迭代都非常方便实用,也是写这类题的常见技巧。