【劍指 の 精選】從宏觀角度看「對稱二叉樹」問題 |Python 主題月

本文正在參加「Python主題月」,詳情查看 活動連接java

題目描述

這是「牛客網」上的「JZ 58 對稱的二叉樹」,難度爲「困難」。markdown

Tag : 「劍指 Offer」、「二叉樹」、「層序遍歷」、「迭代」、「遞歸」app

描述:函數

請實現一個函數,用來判斷一棵二叉樹是否是對稱的。post

注意,若是一個二叉樹同此二叉樹的鏡像是一樣的,定義其爲對稱的。ui

示例1spa

輸入:{8,6,6,5,7,7,5}

返回值:true

複製代碼

示例2設計

輸入:{8,6,9,5,7,7,5}

返回值:false

複製代碼

要求:指針

  • 時間:1 scode

  • 空間:64 M

基本思想

首先要明確,題目所定義的 「對稱」 是對每層而言,同時考慮空節點。

所以,若是咱們使用常規的遍歷方式進行檢查的話,須要對空節點有所表示。

局部檢查(層序遍歷)

咱們使用 0x3f3f3f3f 做爲無效值,並創建佔位節點 emptyNode 用來代指空節點(emptyNode.val = 0x3f3f3f3f)。

一個樸素的作法是:使用「層序遍歷」的方式進行「逐層檢查」,對於空節點使用 emptyNode 進行代指,同時確保不遞歸 emptyNode 對應的子節點。

具體作法以下:

  1. 起始時,將 root 節點入隊;

  2. 從隊列中取出節點,檢查節點是否爲 emptyNode 節點來決定是否繼續入隊:

  • 當不是 emptyNode 節點時,將其左/右兒子進行入隊,若是沒有左/右兒子,則用 emptyNode 代替入隊;

  • 當是 emptyNode 節點時,則忽略;

  1. 在進行流程 的同時使用「臨時列表」記錄當前層的信息,並檢查當前層是否符合 「對稱」 要求;

  2. 循環流程 和 ,直到整個隊列爲空。

Java 代碼:

import java.util.*;
class Solution {
    int INF = 0x3f3f3f3f;
    TreeNode emptyNode = new TreeNode(INF);
    boolean isSymmetrical(TreeNode root) {
        if (root == nullreturn true;

        Deque<TreeNode> d = new ArrayDeque<>();
        d.add(root);
        while (!d.isEmpty()) {
            // 每次循環都將下一層拓展完並存到「隊列」中
            // 同時將該層節點值依次存入到「臨時列表」中
            int size  = d.size();
            List<Integer> list = new ArrayList<>();
            while (size-- > 0) {
                TreeNode poll = d.pollFirst();
                if (!poll.equals(emptyNode)) {
                    d.addLast(poll.left != null ? poll.left : emptyNode);
                    d.addLast(poll.right != null ? poll.right : emptyNode);
                }
                list.add(poll.val);
            }
            
            // 每一層拓展完後,檢查一下存放當前層的該層是否符合「對稱」要求
            if (!check(list)) return false;
        }
        return true;
    }

    // 使用「雙指針」檢查某層是否符合「對稱」要求
    boolean check(List<Integer> list) {
        int l = 0, r = list.size() - 1;
        while (l < r) {
            if (!list.get(l).equals(list.get(r))) return false;
            l++;
            r--;
        }
        return true;
    }
}
複製代碼

Python 3 代碼:

from collections import deque
from math import inf

class Solution:
    emptyNode = TreeNode(inf)
    def isSymmetrical(self, root):
        if root is None:
            return True
        d = deque([])
        d.append(root)
        while d:
            # 每次循環都將下一層拓展完並存到「隊列」中
            # 同時將該層節點值依次存入到「臨時列表」中
            size = len(d)
            lt = []
            while size > 0:
                poll = d.popleft()
                if poll != self.emptyNode:
                    d.append(poll.left if poll.left is not None else self.emptyNode)
                    d.append(poll.right if poll.right is not None else self.emptyNode)
                size -= 1
                lt.append(poll.val)
            # 每一層拓展完後,檢查一下存放當前層的該層是否符合「對稱」要求
            if not self.check(lt):
                return False
        return True
    
    def check(self, lt):
        # 使用「雙指針」檢查某層是否符合「對稱」要求
        l, r = 0, len(lt) - 1
        while l < r:
            if lt[l] != lt[r]:
                return False
            l += 1
            r -= 1
        return True
複製代碼
  • 時間複雜度:在層序遍歷過程當中,每一個節點最多入隊一次,同時在 check 檢查對稱性過程當中,每層只會被檢查一次。複雜度爲 O(n)
  • 空間複雜度:O(n)

總體檢查(遞歸)

在「層序遍歷」解法中,咱們利用了 「對稱」 定義對每層進行檢查。

本質上這是利用 「對稱」 定義進行屢次「局部」檢查。

事實上,咱們還能夠利用 「對稱」 定義在「總體」層面上進行檢查。

咱們如何定義兩棵子樹 ab 是否 「對稱」 ?

當且僅當兩棵子樹符合以下要求時,知足 「對稱」 要求:

  1. 兩棵子樹根節點值相同;

  2. 兩顆子樹的左右子樹分別對稱,包括:

  • a 樹的左子樹與 b 樹的右子樹相應位置的值相等

  • a 樹的右子樹與 b 樹的左子樹相應位置的值相等

圖片

具體的,咱們能夠設計一個遞歸函數 check ,傳入待檢測的兩顆子樹的頭結點 ab(對於本題都傳入 root 便可),在單次查詢中有以下顯而易見的判斷子樹是否 「對稱」 的 Base Case:

  • ab 均爲空節點:符合 「對稱」 要求;

  • ab 其中一個節點爲空,不符合  「對稱」 要求;

  • ab 值不相等,不符合  「對稱」 要求;

其餘狀況,咱們則要分別檢查 ab 的左右節點是否 「對稱」 ,即遞歸調用 check(a.left, b.right)check(a.right, b.left)

Java 代碼:

class Solution {
    public boolean isSymmetrical(TreeNode root) {
        return check(root, root);
    }
    boolean check(TreeNode a, TreeNode b) {
        if (a == null && b == nullreturn true;
        if (a == null || b == nullreturn false;
        if (a.val != b.val) return false;
        return check(a.left, b.right) && check(a.right, b.left);
    }
}
複製代碼

Python 3 代碼:

class Solution:
    def isSymmetrical(self, root):
        return self.check(root, root)

    def check(self, a, b):
        if a is None and b is None:
            return True
        elif a is None or b is None:
            return False
        if a.val != b.val:
            return False
        return self.check(a.left, b.right) and self.check(a.right, b.left)

複製代碼
  • 時間複雜度:每一個節點只會被訪問一次。複雜度爲 O(n)
  • 空間複雜度:忽略遞歸帶來的額外空間開銷。複雜度爲 O(1)

總結

上述兩種解法不單單是實現上的不一樣,更多的是檢查 「出發點」 的不一樣:

  • 解法一:利用「層序遍歷」的方式,以 「層」 爲單位進行 「對稱」 檢查;

  • 解法二:利用「遞歸樹展開」的方式,以 「子樹」 爲單位進行 「對稱」 檢查。

當咱們從總體層面出發考慮時,配合遞歸,每每能寫出比常規作法要簡潔得多的代碼。

建議你們加深對「局部」和「總體」兩種不一樣出發點的理解。

最後

這是咱們「劍指 の 精選」系列文章的第 58 篇,系列開始於 2021/07/01。

該系列會將「劍指 Offer」中比較經典而又不過期的題目都講一遍。

在提供追求「證實」&「思路」的同時,提供最爲簡潔的代碼。

歡迎關注,交個朋友 (`・ω・´)

相關文章
相關標籤/搜索