【LeetCode題解】二叉樹的遍歷
【LeetCode題解】二叉樹的遍歷
我準備開始一個新系列【LeetCode題解】,用來記錄刷題,順便複習一下資料結構與演算法。
1. 二叉樹
二叉樹(binary tree)是一種極為普遍的資料結構,樹的每一個節點最多隻有兩個節點——左孩子結點與右孩子結點。C實現的二叉樹:
struct TreeNode {
int val;
struct TreeNode *left; // left child
struct TreeNode *right; // right child
};
DFS
DFS的思想非常樸素:根據結點的連線關係,依次訪問每一個節點,直至遍歷完整棵樹。根據根節點的訪問次序的不同——前、中、後,可分為先序、中序、後序遍歷。先序遍歷是指先訪問根節點,再依次訪問左孩子節點、右孩子節點。下圖給出一個二叉樹的先序、中序、後序遍歷示例:
遞迴實現:遞迴呼叫,列印根節點。C實現如下:
// preorder binary tree traversal void preorder(struct TreeNode *root) { if(root) { printf("%d", root->val); preorder(root -> left); preorder(root -> right); } } void inorder(struct TreeNode *root) { if(root) { inorder(root -> left); printf("%d", root->val); inorder(root -> right); } } void postorder(struct TreeNode *root) { if(root) { postorder(root -> left); postorder(root -> right); printf("%d", root->val); } }
任何遞迴實現的程式都可以改用棧實現。比如,先序遍歷時用棧維護待訪問結點;先將根結點入棧,再將右孩子結點入棧、左孩子結點入棧。Java實現如下:
public List<Integer> preorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null) return result; Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); result.add(node.val); if (node.right != null) stack.push(node.right); if (node.left != null) stack.push(node.left); } return result; }
中序遍歷,棧維護已訪問結點。一個結點只有當其左孩子結點已訪問時,才能出棧,然後入棧右孩子結點;否則,則入棧左孩子結點。
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.peek();
if (node.left != null) {
stack.push(node.left);
node.left = null; // its left child has been visited
} else {
result.add(node.val);
stack.pop();
if (node.right != null)
stack.push(node.right);
}
}
return result;
}
後序遍歷,棧也維護已訪問結點。一個結點只有當其左右孩子結點均已訪問時,才能出棧;否則,則依次入棧右孩子結點、左孩子結點。
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.peek();
if (node.left == null && node.right == null) {
result.add(node.val);
stack.pop();
} else {
if (node.right != null) {
stack.push(node.right);
node.right = null; // its right child has been visited
}
if (node.left != null) {
stack.push(node.left);
node.left = null; // its left child has been visited
}
}
}
return result;
}
層次遍歷
層次遍歷為二叉樹的BFS,是指按照結點的層級依次從左至右訪問。實現層序遍歷需要藉助於佇列,用以維護待訪問的結點。
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new LinkedList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size();
List<Integer> levelList = new LinkedList<>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
levelList.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result.add(levelList);
}
return result;
}
2. 題解
LeetCode題目 | 歸類 |
---|---|
100. Same Tree | |
101. Symmetric Tree | |
104. Maximum Depth of Binary Tree | |
111. Minimum Depth of Binary Tree | |
110. Balanced Binary Tree | |
112. Path Sum | |
113. Path Sum II | |
129. Sum Root to Leaf Numbers | |
144. Binary Tree Preorder Traversal | 先序 |
94. Binary Tree Inorder Traversal | 中序 |
145. Binary Tree Postorder Traversal | 後序 |
102. Binary Tree Level Order Traversal | 層次 |
107. Binary Tree Level Order Traversal II | 層次 |
103. Binary Tree Zigzag Level Order Traversal | 層次 |
114. Flatten Binary Tree to Linked List |
100. Same Tree
判斷兩棵樹是否一樣,遞迴解決:
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) return true;
if (p == null || q == null) return false;
return p.val == q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
101. Symmetric Tree
同上一題較為類似,判斷一棵樹是否中心對稱:
public boolean isSymmetric(TreeNode root) {
return root == null || helper(root.left, root.right);
}
private boolean helper(TreeNode p, TreeNode q) {
if (p == null && q == null) return true;
if (p == null || q == null) return false;
return p.val == q.val && helper(p.left, q.right) && helper(p.right, q.left);
}
104. Maximum Depth of Binary Tree
二叉樹的最大深度,遞迴實現之:
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
111. Minimum Depth of Binary Tree
二叉樹的最小深度,解決思路與上類似,不過有個special case——根不能視作為葉子節點:
public int minDepth(TreeNode root) {
if (root == null) return 0;
if (root.left == null || root.right == null)
return 1 + Math.max(minDepth(root.left), minDepth(root.right));
return 1 + Math.min(minDepth(root.left), minDepth(root.right));
}
110. Balanced Binary Tree
判斷一棵樹是否平衡,即每一個節點的左右子樹的深度不大於1,正好可用到上面求樹深度的程式碼:
public boolean isBalanced(TreeNode root) {
return root == null || Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
112. Path Sum
判斷root-to-leaf路徑的結點之和是否存在,遞迴實現之:
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) return false;
if (root.val == sum && root.left == null && root.right == null) return true; // root is leaf node
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
113. Path Sum II
上一個問題的變種,要將所有滿足sum的情況儲存下來;解決辦法採用兩個list儲存中間結果,要注意list適當remove:
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<List<Integer>> result = new LinkedList<>();
List<Integer> pathList = new LinkedList<>(); // store a path which meets required sum
helper(root, sum, result, pathList);
return result;
}
private void helper(TreeNode root, int sum, List<List<Integer>> result, List<Integer> pathList) {
if (root != null) {
pathList.add(root.val);
if (root.val == sum && root.left == null && root.right == null) {
result.add(new LinkedList<>(pathList)); // deep copy
} else {
helper(root.left, sum - root.val, result, pathList);
helper(root.right, sum - root.val, result, pathList);
}
pathList.remove(pathList.size() - 1); // remove the last element
}
}
129. Sum Root to Leaf Numbers
二叉樹的root-to-leaf路徑表示一個十進位制數,求所有路徑所代表的數之和,遞迴實現:
public int sumNumbers(TreeNode root) {
return helper(root, 0);
}
private int helper(TreeNode root, int sum) {
if (root == null) return 0; // special case
if (root.left == null && root.right == null) return sum * 10 + root.val;
return helper(root.left, sum * 10 + root.val) + helper(root.right, sum * 10 + root.val);
}
144. Binary Tree Preorder Traversal
先序遍歷,參看前面的棧實現。
94. Binary Tree Inorder Traversal
中序遍歷,同上。
145. Binary Tree Postorder Traversal
後序遍歷,同上。
102. Binary Tree Level Order Traversal
層次遍歷,同上。
107. Binary Tree Level Order Traversal II
層次遍歷變種,不同的是從bottom開始往上遍歷;只需在層次遍歷的程式碼中修改從頭入result連結串列而不是從尾入:
result.addFirst(levelList);
103. Binary Tree Zigzag Level Order Traversa
“之”字形層次遍歷,在層次遍歷的基礎上,將層級分為奇數、偶數,分別按順序、逆序出隊。
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> result = new LinkedList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int level = 1;
while (!queue.isEmpty()) {
int levelSize = queue.size();
List<Integer> levelList = new LinkedList<>();
while (levelSize-- > 0) { // to deal with level nodes
TreeNode node = queue.poll();
levelList.add(node.val);
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
if (level % 2 == 0) // be stack order when level is even
Collections.reverse(levelList);
result.add(levelList);
level++;
}
return result;
}
114. Flatten Binary Tree to Linked List
將二叉樹打散成長鏈條樹,從遞迴的角度來看,可分解為三個步驟:打散左子樹,打散右子樹,然後將右子樹拼接到左子樹尾部。
public void flatten(TreeNode root) {
helper(root, null);
}
// flatten sub-tree, return its root node
private TreeNode helper(TreeNode root, TreeNode last) {
if (root == null) return last;
root.right = helper(root.left, helper(root.right, last));
root.left = null;
return root;
}
如需轉載,請註明作者及出處.
作者:Treant