1. 问题背景与理解
- 同构字符串是LeetCode上的一道经典算法题,属于字符串处理类问题的中等难度题目。这道题考察的是对字符串映射关系的理解和哈希表的应用能力。
题目要求我们判断两个字符串s和t是否是同构的。同构的定义是:如果s中的字符可以按某种映射关系替换得到t,且这种映射关系必须是一一对应的。换句话说:
- s中的每个字符都必须映射到t中唯一的一个字符
- t中的每个字符也必须被s中唯一的一个字符映射
- 这种映射关系必须保持顺序不变
举个例子:
- "egg"和"add"是同构的(e→a,g→d)
- "foo"和"bar"不是同构的(f→b,o→a,但第二个o不能也映射到r)
- "paper"和"title"是同构的(p→t,a→i,e→l,r→e)
2. 解题思路分析
2.1 暴力解法思考
最直观的想法可能是遍历字符串,记录每个字符的映射关系。当发现某个字符的映射与之前记录的不一致时,就返回false。这需要维护两个方向的映射关系:
- s到t的字符映射(确保s中的字符不会映射到多个t字符)
- t到s的字符映射(确保t中的字符不会被多个s字符映射)
2.2 哈希表应用
这种双向映射关系非常适合用哈希表(或字典)来实现。我们可以:
- 创建两个哈希表:s_to_t和t_to_s
- 同时遍历两个字符串的每个字符
- 对于每个位置i:
- 检查s[i]是否已经在s_to_t中
- 如果在,验证其映射是否等于t[i]
- 如果不等,返回false
- 检查t[i]是否已经在t_to_s中
- 如果在,验证其映射是否等于s[i]
- 如果不等,返回false
- 如果都不在,建立双向映射关系
- 检查s[i]是否已经在s_to_t中
- 如果遍历完成没有冲突,返回true
2.3 边界条件考虑
需要特别注意以下边界情况:
- 两个字符串长度不等(直接返回false)
- 空字符串(题目说明s和t长度相同,所以都是空字符串时返回true)
- 所有字符都相同的情况(如"aaa"和"bbb")
- 完全不同的字符(如"abcdef"和"ghijkl")
3. 代码实现与优化
3.1 基础实现
python复制def isIsomorphic(s: str, t: str) -> bool:
if len(s) != len(t):
return False
s_to_t = {}
t_to_s = {}
for s_char, t_char in zip(s, t):
if s_char in s_to_t:
if s_to_t[s_char] != t_char:
return False
else:
s_to_t[s_char] = t_char
if t_char in t_to_s:
if t_to_s[t_char] != s_char:
return False
else:
t_to_s[t_char] = s_char
return True
3.2 优化思路
观察发现,我们其实可以只使用一个方向的映射,然后额外检查映射的值是否已经被使用。这样可以减少一个哈希表的使用:
python复制def isIsomorphic(s: str, t: str) -> bool:
if len(s) != len(t):
return False
mapping = {}
mapped_values = set()
for s_char, t_char in zip(s, t):
if s_char in mapping:
if mapping[s_char] != t_char:
return False
else:
if t_char in mapped_values:
return False
mapping[s_char] = t_char
mapped_values.add(t_char)
return True
3.3 更简洁的实现
还可以利用Python的zip和set特性写出更简洁的代码:
python复制def isIsomorphic(s: str, t: str) -> bool:
return len(set(zip(s, t))) == len(set(s)) == len(set(t))
这个解法的原理是:
- zip(s,t)创建了字符对集合
- 如果是一一映射,那么字符对集合的大小应该等于s和t中唯一字符的数量
4. 复杂度分析与比较
4.1 时间复杂度
所有解法的时间复杂度都是O(n),其中n是字符串长度,因为我们需要遍历整个字符串一次。
4.2 空间复杂度
- 双哈希表解法:O(∣Σ∣),其中Σ是字符集大小(ASCII最多256)
- 单哈希表加集合解法:同上
- 简洁解法:O(n)在最坏情况下(因为要创建三个集合)
虽然大O表示法相同,但实际使用中双哈希表解法通常最快,因为集合操作有一定开销。
5. 测试用例设计
完整的测试应该包含以下情况:
python复制test_cases = [
("egg", "add", True),
("foo", "bar", False),
("paper", "title", True),
("ab", "aa", False),
("ab", "ca", True),
("", "", True),
("a", "a", True),
("abcdefghijklmnopqrstuvwxyz", "zyxwvutsrqponmlkjihgfedcba", True),
("abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyy", False)
]
6. 常见错误与调试
6.1 只检查单向映射
初学者常犯的错误是只检查s到t的映射,而忽略了t到s的映射。例如:
python复制# 错误示例
def isIsomorphic(s: str, t: str) -> bool:
mapping = {}
for s_char, t_char in zip(s, t):
if s_char in mapping:
if mapping[s_char] != t_char:
return False
else:
mapping[s_char] = t_char
return True
这个实现会错误地认为"ab"和"aa"是同构的(实际上不是,因为a和b都映射到了a)。
6.2 忽略字符顺序
另一个常见错误是只检查字符集映射关系而忽略了顺序。例如认为"abc"和"cba"是同构的(实际上不是,因为顺序改变了)。
6.3 边界条件处理
忘记处理空字符串或长度不等的情况也是常见错误。虽然题目说明长度相同,但实际编程中还是应该先检查。
7. 实际应用场景
同构字符串的概念在实际中有多种应用:
- 密码学中的简单替换密码
- 数据编码转换
- 模式匹配中的通配符替换
- 生物信息学中的序列比对
理解这种一一映射关系对于处理各种编码和转换问题很有帮助。
8. 类似题目推荐
掌握了这道题后,可以尝试以下类似题目:
-
- 单词规律(几乎相同的概念,应用于单词和字符)
-
- 查找和替换模式(扩展到多个字符串的匹配)
-
- 同构字符串的变体(更复杂的映射规则)
9. 个人解题心得
在实际解决这个问题时,有几点经验值得分享:
-
双向验证很重要:一开始我只考虑了单向映射,导致部分测试用例失败。这提醒我在处理映射关系时要考虑双向性。
-
测试用例要全面:除了常规情况,特别要测试:
- 所有字符相同
- 完全不同的字符
- 边界长度(空字符串、单字符)
- 长字符串
-
多种解法比较:虽然简洁的Python式解法很优雅,但在性能敏感的场景下,显式的双哈希表解法通常更快且更易理解。
-
字符集考虑:题目没有说明字符集范围,实际中可以假设为ASCII(256个字符),如果是Unicode则需要考虑更大的空间。
-
提前终止优化:在发现不匹配时立即返回false,可以避免不必要的完整遍历。