Description

給定兩個字串 st,判斷它們是否同構。兩個字串同構意味著 s 中的字元可以被替換成 t 中對應的字元,且映射必須是一對一的(不同字元不能映射到同一個字元)。

Example:

Input: s = “egg”, t = “add” Output: true

Intuition#

核心思路:建立雙向映射,確保 s → tt → s 的映射都是一對一的。

  • 光有 s → t 的映射不夠,例如 s = "badc", t = "baba"d → bb → b 會衝突
  • 需要雙向檢查:每個 s 的字元只能映射到一個 t 的字元,反之亦然

Approaches#

1: 雙 HashMap#

  • 概念: 用兩個 HashMap 分別記錄 s → tt → s 的映射
  • 時間複雜度: O(n) - 遍歷一次字串
  • 空間複雜度: O(n) - 兩個 Map
class Solution {
    fun isIsomorphic(s: String, t: String): Boolean {
        val mapST = HashMap<Char, Char>()
        val mapTS = HashMap<Char, Char>()
        for (i in s.indices) {
            val cs = s[i]
            val ct = t[i]
            if (mapST.containsKey(cs) && mapST[cs] != ct) return false
            if (mapTS.containsKey(ct) && mapTS[ct] != cs) return false
            mapST[cs] = ct
            mapTS[ct] = cs
        }
        return true
    }
}

⭐2: 首次出現位置比較#

  • 概念: 對每個位置 i,比較 s[i]s 中首次出現的位置和 t[i]t 中首次出現的位置,若不同則不同構
  • 時間複雜度: O(n^2) - indexOf 每次 O(n);但概念簡潔
  • 空間複雜度: O(1) - 不需要額外空間
class Solution {
    fun isIsomorphic(s: String, t: String): Boolean {
        for (i in s.indices) {
            if (s.indexOf(s[i]) != t.indexOf(t[i])) return false
        }
        return true
    }
}

雖然首次出現位置法概念簡潔,但因 indexOf 是 O(n) 操作,整體時間複雜度為 O(n^2)。面試中推薦使用雙 HashMap 的 O(n) 解法。

補充:使用陣列代替 HashMap

因為字元集有限(ASCII 128),可以用陣列代替 HashMap 來加速。

class Solution {
    fun isIsomorphic(s: String, t: String): Boolean {
        val mapS = IntArray(128)
        val mapT = IntArray(128)
        for (i in s.indices) {
            if (mapS[s[i].code] != mapT[t[i].code]) return false
            mapS[s[i].code] = i + 1
            mapT[t[i].code] = i + 1
        }
        return true
    }
}

🔑 Takeaways#

  • Pattern: 雙向映射驗證是同構/模式匹配問題的標準做法
  • 關鍵技巧: 只有單向映射會遺漏「多對一」的情況;用陣列紀錄「上次出現位置」是一個優雅的替代方案