1. 题目
题目描述
给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以按某种映射关系替换得到 t,那么这两个字符串是同构的。
每个出现的字符都应当映射到另一个字符,同时字符不能映射到多个字符,不同字符不能映射到同一个字符(双向唯一映射)。
字符串长度相等。
示例
输入:s = "egg", t = "add"
输出:true
输入:s = "foo", t = "bar"
输出:false
输入:s = "paper", t = "title"
输出:true
约束
-
(1 <= s.length <= 5 \times 10^4)
-
s、t由任意 ASCII 字符组成,长度相等
2. 最佳解题思路描述(双向哈希映射,你的思路标准解法)
核心要求:双向一一映射
-
建立两张哈希表:
-
map1:s 字符 → t 字符(正向映射)
-
map2:t 字符 → s 字符(反向映射)
-
-
同步遍历两个字符串相同下标字符 c, c2:
情况 1:c 已有正向映射,映射值必须等于 c2,否则不同构;
情况 2:c 无正向映射,但 c2 已有反向映射,说明别的 s 字符占用了 c2,冲突,返回 false;
情况 3:两边都无映射,同时存入两张哈希表;
-
全程无冲突则最后返回 true。
优势
逻辑直观,完美满足双向唯一映射规则;时间 (O(n)),n 为字符串长度。
3. 可优化代码(逻辑完全正确,仅规范、细节优化)
class Solution {
public:
bool isIsomorphic(string s, string t) {
unordered_map<char,char>hash;
unordered_map<char,char>hash2;
for(int i = 0;i<s.size();i++){
char c = s[i];
char c2 = t[i];
auto it = hash.find(c);
auto it2 = hash2.find(c2);
if(it != hash.end()){
if(it->second != c2) return false;
}
else if(it2 != hash2.end()){
if(it2->second != c) return false;
}
else{
hash[c] = c2;
hash2[c2] = c;
}
}
return true;
}
};
代码说明
-
正确性:无逻辑漏洞,所有测试用例均可通过,是标准双向哈希解法;
-
可优化规范点:
-
变量名语义模糊:
hash→s2t,hash2→t2s,可读性更强; -
ASCII 字符范围 0~127,可用长度 128 数组替代 unordered_map,寻址更快、无哈希开销;
-
循环改用范围 for 配合下标,或统一格式化空格;
-
提前缓存字符串长度,避免多次调用
s.size()。
-
4. 两种优化标准代码
版本 1:数组映射(最优,ASCII 固定范围)
class Solution {
public:
bool isIsomorphic(string s, string t) {
// ASCII共128个字符
char s2t[128] = {0};
char t2s[128] = {0};
int n = s.size();
for (int i = 0; i < n; ++i) {
char c1 = s[i], c2 = t[i];
int idx1 = c1, idx2 = c2;
if (s2t[idx1] != 0) {
if (s2t[idx1] != c2) return false;
} else if (t2s[idx2] != 0) {
if (t2s[idx2] != c1) return false;
} else {
s2t[idx1] = c2;
t2s[idx2] = c1;
}
}
return true;
}
};
版本 2:规范哈希表版(哈希思路)
#include <unordered_map>
using namespace std;
class Solution {
public:
bool isIsomorphic(string s, string t) {
unordered_map<char, char> s2t, t2s;
int n = s.size();
for (int i = 0; i < n; ++i) {
char sc = s[i], tc = t[i];
auto it1 = s2t.find(sc);
auto it2 = t2s.find(tc);
if (it1 != s2t.end()) {
if (it1->second != tc) return false;
} else if (it2 != t2s.end()) {
if (it2->second != sc) return false;
} else {
s2t[sc] = tc;
t2s[tc] = sc;
}
}
return true;
}
};
5. 总结
-
双向哈希思路完全正确,核心关键点:
必须双向映射校验,只单向 map 会出错;
反例:s="ab",t="aa",只用一张 map 会误判 true,两张 map 才能拦截;
-
字符为 ASCII 全集时,固定长度数组比 unordered_map 性能更好;
-
冲突判断顺序:先查正向映射,再查反向映射,任一不匹配直接 return false 提前终止。
6. 相关知识拓展
拓展 1:单向映射错误示范(易错坑)
只存 s→t,无法拦截多对一映射,过不了用例 s="ab",t="aa"
// 错误代码
bool isIsomorphic(string s, string t) {
unordered_map<char,char> mp;
for(int i=0;i<s.size();i++){
if(mp.count(s[i]) && mp[s[i]]!=t[i]) return false;
mp[s[i]]=t[i];
}
return true;
}
拓展 2:同构进阶 LC290 单词规律
逻辑完全一致,只是映射对象从字符变为单词,同样需要双向哈希校验。
拓展 3:复杂度对比
-
双数组 ASCII 映射:(O(n)) 时间,(O(1)) 固定 128 空间(最优);
-
双 unordered_map 哈希:(O(n)) 平均时间,(O(n)) 最坏空间;
拓展 4:核心易错点复盘
-
只建立单向映射,无法处理多个字符映射到同一个字符的情况;
-
忘记同步更新两张映射表,导致反向校验失效;
-
哈希表 / 数组初始化不彻底,残留旧数据干扰判断。