二叉樹的遍歷

遞迴:

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 對應的節點,並保證二叉搜尋樹的性質不變。返回二叉搜尋樹(有可能被更新)的根節點的引用。

一般來說,刪除節點可分為兩個步驟:

  1. 首先找到需要刪除的節點;
  2. 如果找到了,刪除它。

說明: 要求演算法時間複雜度為 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;
}
}