力扣解题-205. 同构字符串

6 阅读7分钟

力扣解题-205. 同构字符串

给定两个字符串 s 和 t ,判断它们是否是同构的。

如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。

每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。

示例 1:

输入:s = "egg", t = "add"

输出:true

解释:

字符串 s 和 t 可以通过以下方式变得相同:

将 'e' 映射为 'a'。

将 'g' 映射为 'd'。

示例 2:

输入:s = "f11", t = "b23"

输出:false

解释:

字符串 s 和 t 无法变得相同,因为 '1' 需要同时映射到 '2' 和 '3'。

示例 3:

输入:s = "paper", t = "title"

输出:true

提示:

1 <= s.length <= 5 * 104

t.length == s.length

s 和 t 由任意有效的 ASCII 字符组成

Related Topics

哈希表、字符串


第一次解答

解题思路

核心方法:双向哈希表映射法,通过两个HashMap分别维护s→tt→s的字符映射关系,确保“相同字符映射唯一”且“不同字符不映射到同一字符”,逻辑严谨且能完全满足题目约束,时间复杂度O(n)、空间复杂度O(k)(k为不同字符数)。

核心逻辑拆解

判断同构字符串的核心是“双向唯一映射”:

  1. 正向约束:s中的同一个字符,必须始终映射到t中的同一个字符(比如s的'e'不能既映射到'a'又映射到'b');
  2. 反向约束:t中的同一个字符,只能被s中的一个字符映射(比如s的'e'和'g'不能同时映射到t的'a');
  3. 用两个HashMap分别记录:
    • map:key为s的字符,value为t的字符(正向映射);
    • map2:key为t的字符,value为s的字符(反向映射);
  4. 遍历每个字符位置i:
    • 若s[i]已在map中,检查map.get(s[i])是否等于t[i],不等则返回false;
    • 若s[i]不在map中,检查t[i]是否已在map2中(避免不同s字符映射到同一t字符),若存在则返回false;
    • 若都满足,将s[i]→t[i]存入map,t[i]→s[i]存入map2
  5. 遍历完成后返回true。
具体步骤(以示例1 s="egg",t="add"为例)
  1. i=0(s='e',t='a'):
    • map无'e',map2无'a' → 存入map(e→a)、map2(a→e);
  2. i=1(s='g',t='d'):
    • map无'g',map2无'd' → 存入map(g→d)、map2(d→g);
  3. i=2(s='g',t='d'):
    • map有'g',且map.get(g)=d == t[i] → 验证通过;
  4. 遍历完成,返回true。
性能说明
  • 时间复杂度:O(n)(仅遍历字符串一次,HashMap的增删查操作均为O(1));
  • 空间复杂度:O(k)(k为字符串中不同字符的数量,最多为256个ASCII字符);
  • 优势:逻辑直观,双向映射能完全覆盖题目所有约束(相同字符唯一映射、不同字符不重复映射);
  • 潜在优化点:HashMap的自动装箱/拆箱、哈希计算有轻微开销,可通过数组优化进一步提升性能。
    public boolean isIsomorphic(String s, String t) {
        boolean result=true;
        Map<Character,Character> map = new HashMap<>();
        Map<Character,Character> map2 = new HashMap<>();
        for(int i=0;i<s.length();i++){
            char a=s.charAt(i);
            if(map.containsKey(a)){
                if(map.get(a)!=t.charAt(i)){
                    result=false;
                    break;
                }
            }else {
                if(map2.containsKey(t.charAt(i))){
                    result=false;
                    break;
                }else {
                    map.put(a,t.charAt(i));
                    map2.put(t.charAt(i),a);
                }
            }

        }
        return result;
    }

示例解答

解题思路

解法1:数组映射法(性能最优解)

核心方法:ASCII数组替代哈希表,利用ASCII字符的取值范围(0~255),用两个长度为256的数组分别记录s→tt→s的映射关系,避免HashMap的哈希计算和装箱开销,时间复杂度O(n)、空间复杂度O(1)(数组长度固定为256),是本题的性能最优解。

核心逻辑拆解

ASCII字符的取值范围是0~255,因此可以用数组下标直接映射字符,数组值记录映射的字符编码:

  1. 初始化两个数组s2tt2s,长度均为256,初始值为0(表示未映射);
  2. 遍历每个字符位置i:
    • 取s[i]的编码sc = s.charAt(i),t[i]的编码tc = t.charAt(i)
    • s2t[sc] != 0(已映射),检查s2t[sc]是否等于tc,不等则返回false;
    • t2s[tc] != 0(反向已映射),检查t2s[tc]是否等于sc,不等则返回false;
    • 若都未映射,设置s2t[sc] = tct2s[tc] = sc
  3. 遍历完成返回true。
代码实现
public boolean isIsomorphic(String s, String t) {
    // 初始化ASCII映射数组,0表示未映射
    int[] s2t = new int[256];
    int[] t2s = new int[256];
    // 填充初始值为-1(避免与字符编码0冲突,比如ASCII 0是空字符)
    Arrays.fill(s2t, -1);
    Arrays.fill(t2s, -1);
    
    int n = s.length();
    for (int i = 0; i < n; i++) {
        char sc = s.charAt(i);
        char tc = t.charAt(i);
        // 正向映射已存在但不匹配
        if (s2t[sc] != -1 && s2t[sc] != tc) {
            return false;
        }
        // 反向映射已存在但不匹配
        if (t2s[tc] != -1 && t2s[tc] != sc) {
            return false;
        }
        // 建立双向映射
        s2t[sc] = tc;
        t2s[tc] = sc;
    }
    return true;
}
性能优势
  • 时间复杂度:O(n)(遍历一次字符串,数组访问为O(1)且无额外开销);
  • 空间复杂度:O(1)(数组长度固定为256,与输入规模无关);
  • 核心优化点:
    1. 数组替代HashMap:避免哈希冲突、自动装箱/拆箱、方法调用等开销,访问速度是HashMap的数倍;
    2. 直接操作字符编码:无需Character对象,减少内存占用;
  • 适用场景:大数据量(如n=5×10⁴)下,性能远超哈希表解法。
解法2:字符首次出现位置映射法(拓展思路)

核心方法:统一字符的首次出现位置,将两个字符串转换为“字符首次出现位置”的序列,若序列完全一致则为同构字符串,无需维护双向映射,逻辑更简洁。

代码实现
public boolean isIsomorphic(String s, String t) {
    int n = s.length();
    // 记录字符首次出现的位置(初始为0)
    int[] sPos = new int[256];
    int[] tPos = new int[256];
    
    for (int i = 0; i < n; i++) {
        char sc = s.charAt(i);
        char tc = t.charAt(i);
        // 首次出现位置不同则不同构
        if (sPos[sc] != tPos[tc]) {
            return false;
        }
        // 更新首次出现位置为i+1(避免与初始值0冲突)
        sPos[sc] = i + 1;
        tPos[tc] = i + 1;
    }
    return true;
}
逻辑说明
  • 核心原理:同构字符串的字符“出现规律”完全一致,比如:
    • s="egg"的首次出现序列:e(1)、g(2)、g(2);
    • t="add"的首次出现序列:a(1)、d(2)、d(2);
    • 序列一致则同构;
  • 优势:仅需两个数组,无需维护双向映射,代码更简洁;
  • 注意点:位置需从1开始(避免初始值0与第一个位置0冲突)。

总结

  1. 双向哈希表法(第一次解答):逻辑直观,能清晰体现“双向唯一映射”的核心约束,适合新手理解;
  2. 数组映射法(最优解):性能最优,利用ASCII字符特性避免哈希表开销,空间复杂度O(1),工程首选;
  3. 首次位置映射法:思路巧妙,代码简洁,无需维护双向映射,适合拓展解题思路;
  4. 关键技巧:
    • 字符类映射问题,若字符范围已知(如ASCII),优先用数组替代HashMap提升性能;
    • 同构字符串的核心是“双向唯一映射”,需同时满足“正向唯一”和“反向唯一”;
    • 初始值避免冲突:用-1或i+1替代0,防止空字符/第一个位置的误判。