Description

對一棵二元樹,我們可以選擇任意節點並交換其左右子樹。若經過若干次翻轉後兩棵樹相同,則稱它們是翻轉等價的。判斷兩棵二元樹是否翻轉等價。

Example:

Input: root1 = [1,2,3,4,5,6,null,null,null,7,8], root2 = [1,3,2,null,6,4,5,null,null,null,null,8,7] Output: true

Intuition#

兩棵樹翻轉等價,代表對應節點的值相同,且子樹要嘛直接匹配、要嘛交叉匹配。

  • 兩個節點值相同時,有兩種可能:
    • 左對左、右對右(不翻轉)
    • 左對右、右對左(翻轉)
  • 只要其中一種成立即可

Approaches#

1: 遞迴比較#

  • 概念: 遞迴檢查每對節點,值相同時嘗試兩種配對方式
  • 時間複雜度: O(n),每個節點最多訪問一次
  • 空間複雜度: O(h),h 為樹高
class Solution {
    fun flipEquiv(root1: TreeNode?, root2: TreeNode?): Boolean {
        if (root1 == null && root2 == null) return true
        if (root1 == null || root2 == null) return false
        if (root1.`val` != root2.`val`) return false

        // 不翻轉 或 翻轉
        return (flipEquiv(root1.left, root2.left) && flipEquiv(root1.right, root2.right)) ||
               (flipEquiv(root1.left, root2.right) && flipEquiv(root1.right, root2.left))
    }
}

⭐ 2: 正規化後比較#

  • 概念: 將兩棵樹「正規化」——每個節點保證左子值小於右子值,然後直接比較是否相同
  • 時間複雜度: O(n)
  • 空間複雜度: O(h)
class Solution {
    fun flipEquiv(root1: TreeNode?, root2: TreeNode?): Boolean {
        canonicalize(root1)
        canonicalize(root2)
        return isSame(root1, root2)
    }

    private fun canonicalize(node: TreeNode?) {
        if (node == null) return
        val leftVal = node.left?.`val` ?: Int.MAX_VALUE
        val rightVal = node.right?.`val` ?: Int.MAX_VALUE
        if (leftVal > rightVal) {
            val temp = node.left
            node.left = node.right
            node.right = temp
        }
        canonicalize(node.left)
        canonicalize(node.right)
    }

    private fun isSame(a: TreeNode?, b: TreeNode?): Boolean {
        if (a == null && b == null) return true
        if (a == null || b == null) return false
        return a.`val` == b.`val` && isSame(a.left, b.left) && isSame(a.right, b.right)
    }
}

🔑 Takeaways#

  • Pattern: 翻轉等價 = 值相同 + 子樹配對(直接或交叉)
  • 關鍵技巧: 遞迴的兩種配對方式用 || 連接,短路求值讓平均效能很好;正規化方法將問題轉化為簡單的相等比較