二叉樹的遍歷
遞迴:
void traverse (TreeNode root) {
if (root == null) {
return null;
}
//前序遍歷位置
traverse(root.left);
//中序遍歷位置
traverse(root.right);
//後序遍歷位置
}
144. 二叉樹的前序遍歷
前序非遞迴:
public static List<Integer> preOrder(TreeNode root) {
if (root == null) {
return null;
}
List<Integer> res = new LinkedList<>();
Deque<TreeNode> stack = new LinkedList<>();
stack.push(root);
while (!stack.isEmpty()) {
// 先遍歷根結點
TreeNode node = stack.pop();
res.add(node.val);
// 列印順序為:根 左 右,因此先將右子結點入棧
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return res;
}
94. 二叉樹的中序遍歷
中序非遞迴:
public static List<Integer> infixOrder(TreeNode root) {
if (root == null) {
return null;
}
List<Integer> res = new LinkedList<>();
TreeNode temp = root;
Deque<TreeNode> stack = new LinkedList<>();
while (temp != null || !stack.isEmpty()) {
while (temp != null) {
stack.push(temp); // 加入棧
temp = temp.left; // 到最左邊結點停止
}
temp = stack.pop(); // 訪問棧頂元素
res.add(temp.val);
temp = temp.right; //下一個遍歷的元素是temp的右子樹的最左邊結點
}
return res;
}
145. 二叉樹的後序遍歷
後序非遞迴:
// 後序可參照前序:後序為:左右根,我們只需按照:根右左遍歷然後翻轉即可
public static List<Integer> postOrder(TreeNode root) {
if (root == null) {
return null;
}
LinkedList<Integer> res = new LinkedList<>();
Deque<TreeNode> stack = new LinkedList<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode temp = stack.pop();
// 每次新增到頭,最後輸出的結果自然為:根右左的逆序
res.addFirst(temp.val);
if (temp.left != null) {
stack.push(temp.left);
}
if (temp.right != null) {
stack.push(temp.right);
}
}
return res;
}
注意:如果非遞迴解法難以理解,可以先按照上面的程式碼結合案例手推一下。重要的還是要先形成模板並記憶,間隔著多做幾次也就慢慢理解了。
102. 二叉樹的層序遍歷
給你一個二叉樹,請你返回其按 層序遍歷 得到的節點值。 (即逐層地,從左到右訪問所有節點)。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new LinkedList<>();
if (root == null) {
return res;
}
// 建立佇列並加入頭結點
Deque<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
// 獲取當前層的結點個數
int size = queue.size();
List<Integer> rowList = new LinkedList<>();
// 將當前層結點按照先進先出(從左至右)的方式出隊,同時將非空子結點加入隊尾
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
rowList.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
res.add(rowList);
}
return res;
}
}
104. 二叉樹的最大深度
給定一個二叉樹,找出其最大深度。
二叉樹的深度為根節點到最遠葉子節點的最長路徑上的節點數。
lass Solution {
/**
* 定義:
* 返回以root為根結點的最大深度
*/
public int maxDepth(TreeNode root) {
//base case, root為空說明樹的高度為0,退出遞迴
if (root == null) {
return 0;
}
/**
* 根據定義,就根節點來說,樹的最大深度為:
* 左右子樹的最大深度中的最大值 + 1
*/
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return 1 + Math.max(left, right);
}
}
- 二叉樹相關的很多題目都是由二叉樹的三種遞迴遍歷演化而來
- 此題其實就是二叉樹的後序遍歷演化而來,要知道當前二叉樹的最大深度自然要先知道兩棵子樹的最大深度,因此用後序遍歷(自底向上)
- 編寫遞迴程式切記不要用腦袋模擬遞迴棧,函式定義好後,根據定義編寫程式碼即可
110. 平衡二叉樹
給定一個二叉樹,判斷它是否是高度平衡的二叉樹。
class Solution {
//先假定是平衡二叉樹
private boolean balance = true;
public boolean isBalanced(TreeNode root) {
height(root);
return balance;
}
//後序遍歷而來,自底向上獲取兩棵子樹的高度,並檢查節點左右子樹高度只差是否小於等於1
private int height(TreeNode root) {
if (root == null) {
return 0;
}
int left = height(root.left);
int right = height(root.right);
if (Math.abs(left - right) > 1) {
balance = false;
}
return Math.max(left, right) + 1;
}
}
124. 二叉樹中的最大路徑和
路徑 被定義為一條從樹中任意節點出發,沿父節點 - 子節點連線,達到任意節點的序列。同一個節點在一條路徑序列中 至多出現一次 。該路徑 至少包含一個 節點,且不一定經過根節點。
路徑和 是路徑中各節點值的總和。
給你一個二叉樹的根節點
root
,返回其 最大路徑和 。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int maxPathSum(TreeNode root) {
maxSumToDescendant(root);
return maxPathSum;
}
//先將 樹的最大路徑和 初始化為最小值
private int maxPathSum = Integer.MIN_VALUE;
// 函式定義:當前結點到 子孫(不一定包含葉子結點) 結點的最大路徑和(最少為其自身一個結點)
private int maxSumToDescendant(TreeNode root) {
if (root == null) {
return 0;
}
// 小於0則認為對最大路徑和沒有貢獻
int left = Math.max(0, maxSumToDescendant(root.left));
int right = Math.max(0, maxSumToDescendant(root.right));
// 自底向上返回的過程中順帶計算 樹的最大路徑和(當前結點到左邊子孫結點的最大路徑和 + 當前結點 + 當前結點到右邊子孫結點的最大路徑和)
maxPathSum = Math.max(maxPathSum, left + root.val + right);
return root.val + Math.max(left, right);
}
}
236. 二叉樹的最近公共祖先
給定一個二叉樹, 找到該樹中兩個指定節點的最近公共祖先。
思路:後序遍歷演變而來,若找到其中一個結點就自底向上返回。若p、q互不為對方子樹中的結點,p、q最終會在某個結點相遇,該節點就為最近公共祖先;否則p或q即為最近公共結點。
class Solution {
//重要已知:p != q
// p 和 q 均存在於給定的二叉樹中。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) {
return root;
}
return left != null ? left : right;
}
}
107. 二叉樹的層序遍歷 II
給定一個二叉樹,返回其節點值自底向上的層序遍歷。 (即按從葉子節點所在層到根節點所在的層,逐層從左向右遍歷)
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
LinkedList<List<Integer>> res = new LinkedList<>();
if (root == null) {
return res;
}
Deque<TreeNode> queue = new LinkedList<>();
queue.offerLast(root);
while (!queue.isEmpty()) {
//獲取當前層的結點數量
int size = queue.size();
//暫存當前層的所有結點
List<Integer> tempList = new LinkedList<>();
TreeNode tempNode;
for (int i = 0; i < size; i++) {
tempNode = queue.poll();
tempList.add(tempNode.val);
if (tempNode.left != null) {
queue.offer(tempNode.left);
}
if (tempNode.right != null) {
queue.offer(tempNode.right);
}
}
//將每一層的結果 逆序 放入最終的list中
res.addFirst(tempList);
}
return res;
}
}
103. 二叉樹的鋸齒形層序遍歷
給定一個二叉樹,返回其節點值的鋸齒形層序遍歷。(即先從左往右,再從右往左進行下一層遍歷,以此類推,層與層之間交替進行)。
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new LinkedList<>();
if (root == null) {
return res;
}
Deque<TreeNode> queue = new LinkedList<>();
queue.offerLast(root);
boolean flag = true;
while(!queue.isEmpty()) {
//獲取當前層的結點數量
int size = queue.size();
LinkedList<Integer> tempList = new LinkedList<>();
TreeNode tempNode;
for (int i = 0; i < size; i++) {
tempNode = queue.pollFirst();
if (flag == true) {
//從前往後,順序存放
tempList.addLast(tempNode.val);
} else {
//從前往後,逆序存放
tempList.addFirst(tempNode.val);
}
if (tempNode.left != null) {
queue.offerLast(tempNode.left);
}
if (tempNode.right != null) {
queue.offerLast(tempNode.right);
}
}
res.add(tempList);
//每遍歷完一層切換flag
flag = !flag;
}
return res;
}
}
二叉搜尋樹
98. 驗證二叉搜尋樹
給定一個二叉樹,判斷其是否是一個有效的二叉搜尋樹。
假設一個二叉搜尋樹具有如下特徵:
- 節點的左子樹只包含小於當前節點的數。
- 節點的右子樹只包含大於當前節點的數。
- 所有左子樹和右子樹自身必須也是二叉搜尋樹。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isValidBST(TreeNode root) {
return isValidBST(root, null, null);
}
//定義:當前結點為根結點的二叉樹是否為二叉搜尋樹。二叉搜尋樹的每個結點都有一個上下界(除了根節點)
private boolean isValidBST(TreeNode node, TreeNode low, TreeNode high) {
//base case
if (node == null) {
return true;
}
//base case,當前結點小於等於下界或大於等於上界都不滿足二叉搜尋樹
if (low != null && node.val <= low.val) return false;
if (high != null && node.val >= high.val) return false;
boolean ret = isValidBST(node.left, low, node) && isValidBST(node.right, node, high);
return ret;
}
}
推薦題解:驗證二叉搜尋樹(BST:給子樹上所有節點都加一個邊界☀)
701. 二叉搜尋樹中的插入操作
給定二叉搜尋樹(
BST
)的根節點和要插入樹中的值,將值插入二叉搜尋樹。 返回插入後二叉搜尋樹的根節點。 輸入資料 保證 ,新值和原始二叉搜尋樹中的任意節點值都不同。注意,可能存在多種有效的插入方式,只要樹在插入後仍保持為二叉搜尋樹即可。 你可以返回 任意有效的結果 。
class Solution {
// 定義:將當前值插入以當前結點為根的二叉搜尋樹並返回根節點
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
return new TreeNode(val);
}
// 一定要根據定義來編寫遞迴程式碼
if (root.val > val) {
// val 小於當前結點值則插入左子樹
root.left = insertIntoBST(root.left, val);
} else {
root.right = insertIntoBST(root.right, val);
}
return root;
}
}
450. 刪除二叉搜尋樹中的節點
給定一個二叉搜尋樹的根節點 root 和一個值 key,刪除二叉搜尋樹中的 key 對應的節點,並保證二叉搜尋樹的性質不變。返回二叉搜尋樹(有可能被更新)的根節點的引用。
一般來說,刪除節點可分為兩個步驟:
- 首先找到需要刪除的節點;
- 如果找到了,刪除它。
說明: 要求演算法時間複雜度為 O(h),h 為樹的高度。
class Solution {
// 定義:刪除當前結點為根結點的二叉搜尋樹中值為 key 的結點
public TreeNode deleteNode(TreeNode root, int key) {
// base case
if (root == null) {
return root;
}
// 切記根據定義編寫遞迴程式碼
if (root.val == key) {
//base case, 刪除結點是葉子結點或只是有一個子樹的非葉結點
if (root.left == null) return root.right;
if (root.right == null) return root.left;
// 有兩個子樹的非葉子結點,用右子樹的最小結點替換當前結點,然後刪除右子樹最小結點
TreeNode node = getMin(root.right);
root.val = node.val;
root.right = deleteNode(root.right, node.val);
// key 大於當前結點值 則根據定義在右子樹中刪除
} else if (root.val < key) {
root.right = deleteNode(root.right, key);
} else if (root.val > key){
root.left = deleteNode(root.left, key);
}
return root;
}
// 獲取root為根的子樹的最小結點(最左邊結點)
private TreeNode getMin(TreeNode root) {
while(root.left != null) {
root = root.left;
}
return root;
}
}