本文正在參加「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
對應的子節點。
具體作法以下:
起始時,將 root
節點入隊;
從隊列中取出節點,檢查節點是否爲 emptyNode
節點來決定是否繼續入隊:
當不是 emptyNode
節點時,將其左/右兒子進行入隊,若是沒有左/右兒子,則用 emptyNode
代替入隊;
當是 emptyNode
節點時,則忽略;
在進行流程 的同時使用「臨時列表」記錄當前層的信息,並檢查當前層是否符合 「對稱」 要求;
循環流程 和 ,直到整個隊列爲空。
Java 代碼:
import java.util.*;
class Solution {
int INF = 0x3f3f3f3f;
TreeNode emptyNode = new TreeNode(INF);
boolean isSymmetrical(TreeNode root) {
if (root == null) return 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)在「層序遍歷」解法中,咱們利用了 「對稱」 定義對每層進行檢查。
本質上這是利用 「對稱」 定義進行屢次「局部」檢查。
事實上,咱們還能夠利用 「對稱」 定義在「總體」層面上進行檢查。
咱們如何定義兩棵子樹 a
和 b
是否 「對稱」 ?
當且僅當兩棵子樹符合以下要求時,知足 「對稱」 要求:
兩棵子樹根節點值相同;
兩顆子樹的左右子樹分別對稱,包括:
a
樹的左子樹與 b
樹的右子樹相應位置的值相等
a
樹的右子樹與 b
樹的左子樹相應位置的值相等
具體的,咱們能夠設計一個遞歸函數 check
,傳入待檢測的兩顆子樹的頭結點 a
和 b
(對於本題都傳入 root
便可),在單次查詢中有以下顯而易見的判斷子樹是否 「對稱」 的 Base Case:
a
和 b
均爲空節點:符合 「對稱」 要求;
a
和 b
其中一個節點爲空,不符合 「對稱」 要求;
a
和 b
值不相等,不符合 「對稱」 要求;
其餘狀況,咱們則要分別檢查 a
和 b
的左右節點是否 「對稱」 ,即遞歸調用 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 == null) return true;
if (a == null || b == null) return 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)
複製代碼
上述兩種解法不單單是實現上的不一樣,更多的是檢查 「出發點」 的不一樣:
解法一:利用「層序遍歷」的方式,以 「層」 爲單位進行 「對稱」 檢查;
解法二:利用「遞歸樹展開」的方式,以 「子樹」 爲單位進行 「對稱」 檢查。
當咱們從總體層面出發考慮時,配合遞歸,每每能寫出比常規作法要簡潔得多的代碼。
建議你們加深對「局部」和「總體」兩種不一樣出發點的理解。
這是咱們「劍指 の 精選」系列文章的第 58
篇,系列開始於 2021/07/01。
該系列會將「劍指 Offer」中比較經典而又不過期的題目都講一遍。
在提供追求「證實」&「思路」的同時,提供最爲簡潔的代碼。
歡迎關注,交個朋友 (`・ω・´)