- 1. 樹的高度
- 2. 平衡樹
- 3. 兩節點的最長路徑
- 4. 翻轉樹
- 5. 歸並兩棵樹
- 6. 判斷路徑和是否等於一個數
- 7. 統計路徑和等於一個數的路徑數量
- 8. 子樹
- 9. 樹的對稱
- 10. 最小路徑
- 11. 統計左葉子節點的和
- 12. 相同節點值的最大路徑長度
- 13. 間隔遍歷
- 14. 找出二叉樹中第二小的節點
- 層次遍歷
- 前中后序遍歷
- BST
- Trie
遞歸
一棵樹要么是空樹,要么有兩個指針,每個指針指向一棵樹。樹是一種遞歸結構,很多樹的問題可以使用遞歸來處理。
1. 樹的高度
104. Maximum Depth of Binary Tree (Easy)
public int maxDepth(TreeNode root) { if (root == null) return 0; return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; }
2. 平衡樹
110. Balanced Binary Tree (Easy)
3
/ \
9 20
/ \
15 7
平衡樹左右子樹高度差都小於等於 1
private boolean result = true; public boolean isBalanced(TreeNode root) { maxDepth(root); return result; } public int maxDepth(TreeNode root) { if (root == null) return 0; int l = maxDepth(root.left); int r = maxDepth(root.right); if (Math.abs(l - r) > 1) result = false; return 1 + Math.max(l, r); }
3. 兩節點的最長路徑
543. Diameter of Binary Tree (Easy)
Input:
1
/ \
2 3
/ \
4 5
Return 3, which is the length of the path [4,2,1,3] or [5,2,1,3].
private int max = 0; public int diameterOfBinaryTree(TreeNode root) { depth(root); return max; } private int depth(TreeNode root) { if (root == null) return 0; int leftDepth = depth(root.left); int rightDepth = depth(root.right); max = Math.max(max, leftDepth + rightDepth); return Math.max(leftDepth, rightDepth) + 1; }
4. 翻轉樹
226. Invert Binary Tree (Easy)
public TreeNode invertTree(TreeNode root) { if (root == null) return null; TreeNode left = root.left; // 后面的操作會改變 left 指針,因此先保存下來 root.left = invertTree(root.right); root.right = invertTree(left); return root; }
5. 歸並兩棵樹
617. Merge Two Binary Trees (Easy)
Input:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
Output:
3
/ \
4 5
/ \ \
5 4 7
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { if (t1 == null && t2 == null) return null; if (t1 == null) return t2; if (t2 == null) return t1; TreeNode root = new TreeNode(t1.val + t2.val); root.left = mergeTrees(t1.left, t2.left); root.right = mergeTrees(t1.right, t2.right); return root; }
6. 判斷路徑和是否等於一個數
Leetcdoe : 112. Path Sum (Easy)
Given the below binary tree and sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.
路徑和定義為從 root 到 leaf 的所有節點的和。
public boolean hasPathSum(TreeNode root, int sum) { if (root == null) return false; if (root.left == null && root.right == null && root.val == sum) return true; return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val); }
7. 統計路徑和等於一個數的路徑數量
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
10
/ \
5 -3
/ \ \
3 2 11
/ \ \
3 -2 1
Return 3. The paths that sum to 8 are:
1. 5 -> 3
2. 5 -> 2 -> 1
3. -3 -> 11
路徑不一定以 root 開頭,也不一定以 leaf 結尾,但是必須連續。
public int pathSum(TreeNode root, int sum) { if (root == null) return 0; int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum); return ret; } private int pathSumStartWithRoot(TreeNode root, int sum) { if (root == null) return 0; int ret = 0; if (root.val == sum) ret++; ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val); return ret; }
8. 子樹
572. Subtree of Another Tree (Easy)
Given tree s:
3
/ \
4 5
/ \
1 2
Given tree t:
4
/ \
1 2
Return true, because t has the same structure and node values with a subtree of s.
Given tree s:
3
/ \
4 5
/ \
1 2
/
0
Given tree t:
4
/ \
1 2
Return false.
public boolean isSubtree(TreeNode s, TreeNode t) { if (s == null) return false; return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t); } private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) { if (t == null && s == null) return true; if (t == null || s == null) return false; if (t.val != s.val) return false; return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right); }
9. 樹的對稱
1
/ \
2 2
/ \ / \
3 4 4 3
public boolean isSymmetric(TreeNode root) { if (root == null) return true; return isSymmetric(root.left, root.right); } private boolean isSymmetric(TreeNode t1, TreeNode t2) { if (t1 == null && t2 == null) return true; if (t1 == null || t2 == null) return false; if (t1.val != t2.val) return false; return isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left); }
10. 最小路徑
111. Minimum Depth of Binary Tree (Easy)
樹的根節點到葉子節點的最小路徑長度
public int minDepth(TreeNode root) { if (root == null) return 0; int left = minDepth(root.left); int right = minDepth(root.right); if (left == 0 || right == 0) return left + right + 1; return Math.min(left, right) + 1; }
11. 統計左葉子節點的和
404. Sum of Left Leaves (Easy)
3
/ \
9 20
/ \
15 7
There are two left leaves in the binary tree, with values 9 and 15 respectively. Return 24.
public int sumOfLeftLeaves(TreeNode root) { if (root == null) return 0; if (isLeaf(root.left)) return root.left.val + sumOfLeftLeaves(root.right); return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right); } private boolean isLeaf(TreeNode node){ if (node == null) return false; return node.left == null && node.right == null; }
12. 相同節點值的最大路徑長度
687. Longest Univalue Path (Easy)
1
/ \
4 5
/ \ \
4 4 5
Output : 2
private int path = 0; public int longestUnivaluePath(TreeNode root) { dfs(root); return path; } private int dfs(TreeNode root){ if (root == null) return 0; int left = dfs(root.left); int right = dfs(root.right); int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0; int rightPath = root.right != null && root.right.val == root.val ? right + 1 : 0; path = Math.max(path, leftPath + rightPath); return Math.max(leftPath, rightPath); }
13. 間隔遍歷
337. House Robber III (Medium)
3
/ \
2 3
\ \
3 1
Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.
public int rob(TreeNode root) { if (root == null) return 0; int val1 = root.val; if (root.left != null) val1 += rob(root.left.left) + rob(root.left.right); if (root.right != null) val1 += rob(root.right.left) + rob(root.right.right); int val2 = rob(root.left) + rob(root.right); return Math.max(val1, val2); }
14. 找出二叉樹中第二小的節點
671. Second Minimum Node In a Binary Tree (Easy)
Input:
2
/ \
2 5
/ \
5 7
Output: 5
一個節點要么具有 0 個或 2 個子節點,如果有子節點,那么根節點是最小的節點。
public int findSecondMinimumValue(TreeNode root) { if (root == null) return -1; if (root.left == null && root.right == null) return -1; int leftVal = root.left.val; int rightVal = root.right.val; if (leftVal == root.val) leftVal = findSecondMinimumValue(root.left); if (rightVal == root.val) rightVal = findSecondMinimumValue(root.right); if (leftVal != -1 && rightVal != -1) return Math.min(leftVal, rightVal); if (leftVal != -1) return leftVal; return rightVal; }
層次遍歷
使用 BFS 進行層次遍歷。不需要使用兩個隊列來分別存儲當前層的節點和下一層的節點,因為在開始遍歷一層的節點時,當前隊列中的節點數就是當前層的節點數,只要控制遍歷這么多節點數,就能保證這次遍歷的都是當前層的節點。
1. 一棵樹每層節點的平均數
637. Average of Levels in Binary Tree (Easy)
public List<Double> averageOfLevels(TreeNode root) { List<Double> ret = new ArrayList<>(); if (root == null) return ret; Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int cnt = queue.size(); double sum = 0; for (int i = 0; i < cnt; i++) { TreeNode node = queue.poll(); sum += node.val; if (node.left != null) queue.add(node.left); if (node.right != null) queue.add(node.right); } ret.add(sum / cnt); } return ret; }
2. 得到左下角的節點
513. Find Bottom Left Tree Value (Easy)
Input:
1
/ \
2 3
/ / \
4 5 6
/
7
Output:
7
public int findBottomLeftValue(TreeNode root) { Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { root = queue.poll(); if (root.right != null) queue.add(root.right); if (root.left != null) queue.add(root.left); } return root.val; }
前中后序遍歷
1
/ \
2 3
/ \ \
4 5 6
- 層次遍歷順序:[1 2 3 4 5 6]
- 前序遍歷順序:[1 2 4 5 3 6]
- 中序遍歷順序:[4 2 5 1 3 6]
- 后序遍歷順序:[4 5 2 6 3 1]
層次遍歷使用 BFS 實現,利用的就是 BFS 一層一層遍歷的特性;而前序、中序、后序遍歷利用了 DFS 實現。
前序、中序、后序遍只是在對節點訪問的順序有一點不同,其它都相同。
① 前序
void dfs(TreeNode root) { visit(root); dfs(root.left); dfs(root.right); }
② 中序
void dfs(TreeNode root) { dfs(root.left); visit(root); dfs(root.right); }
③ 后序
void dfs(TreeNode root) { dfs(root.left); dfs(root.right); visit(root); }
1. 非遞歸實現二叉樹的前序遍歷
144. Binary Tree Preorder Traversal (Medium)
public List<Integer> preorderTraversal(TreeNode root) { List<Integer> ret = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); if (node == null) continue; ret.add(node.val); stack.push(node.right); // 先右后左,保證左子樹先遍歷 stack.push(node.left); } return ret; }
2. 非遞歸實現二叉樹的后序遍歷
145. Binary Tree Postorder Traversal (Medium)
前序遍歷為 root -> left -> right,后序遍歷為 left -> right -> root。可以修改前序遍歷成為 root -> right -> left,那么這個順序就和后序遍歷正好相反。
public List<Integer> postorderTraversal(TreeNode root) { List<Integer> ret = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); if (node == null) continue; ret.add(node.val); stack.push(node.left); stack.push(node.right); } Collections.reverse(ret); return ret; }
3. 非遞歸實現二叉樹的中序遍歷
94. Binary Tree Inorder Traversal (Medium)
public List<Integer> inorderTraversal(TreeNode root) { List<Integer> ret = new ArrayList<>(); if (root == null) return ret; Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root; while (cur != null || !stack.isEmpty()) { while (cur != null) { stack.push(cur); cur = cur.left; } TreeNode node = stack.pop(); ret.add(node.val); cur = node.right; } return ret; }
BST
二叉查找樹(BST):根節點大於等於左子樹所有節點,小於等於右子樹所有節點。
二叉查找樹中序遍歷有序。
1. 修剪二叉查找樹
669. Trim a Binary Search Tree (Easy)
Input:
3
/ \
0 4
\
2
/
1
L = 1
R = 3
Output:
3
/
2
/
1
題目描述:只保留值在 L ~ R 之間的節點
public TreeNode trimBST(TreeNode root, int L, int R) { if (root == null) return null; if (root.val > R) return trimBST(root.left, L, R); if (root.val < L) return trimBST(root.right, L, R); root.left = trimBST(root.left, L, R); root.right = trimBST(root.right, L, R); return root; }
2. 尋找二叉查找樹的第 k 個元素
230. Kth Smallest Element in a BST (Medium)
中序遍歷解法:
private int cnt = 0; private int val; public int kthSmallest(TreeNode root, int k) { inOrder(root, k); return val; } private void inOrder(TreeNode node, int k) { if (node == null) return; inOrder(node.left, k); cnt++; if (cnt == k) { val = node.val; return; } inOrder(node.right, k); }
遞歸解法:
public int kthSmallest(TreeNode root, int k) { int leftCnt = count(root.left); if (leftCnt == k - 1) return root.val; if (leftCnt > k - 1) return kthSmallest(root.left, k); return kthSmallest(root.right, k - leftCnt - 1); } private int count(TreeNode node) { if (node == null) return 0; return 1 + count(node.left) + count(node.right); }
3. 把二叉查找樹每個節點的值都加上比它大的節點的值
Convert BST to Greater Tree (Easy)
Input: The root of a Binary Search Tree like this:
5
/ \
2 13
Output: The root of a Greater Tree like this:
18
/ \
20 13
先遍歷右子樹。
private int sum = 0; public TreeNode convertBST(TreeNode root) { traver(root); return root; } private void traver(TreeNode node) { if (node == null) return; traver(node.right); sum += node.val; node.val = sum; traver(node.left); }
4. 二叉查找樹的最近公共祖先
235. Lowest Common Ancestor of a Binary Search Tree (Easy)
_______6______
/ \
___2__ ___8__
/ \ / \
0 4 7 9
/ \
3 5
For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6. Another example is LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q); if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q); return root; }
5. 二叉樹的最近公共祖先
236. Lowest Common Ancestor of a Binary Tree (Medium)
_______3______
/ \
___5__ ___1__
/ \ / \
6 2 0 8
/ \
7 4
For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3. Another example is LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.
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); return left == null ? right : right == null ? left : root; }
6. 從有序數組中構造二叉查找樹
108. Convert Sorted Array to Binary Search Tree (Easy)
public TreeNode sortedArrayToBST(int[] nums) { return toBST(nums, 0, nums.length - 1); } private TreeNode toBST(int[] nums, int sIdx, int eIdx){ if (sIdx > eIdx) return null; int mIdx = (sIdx + eIdx) / 2; TreeNode root = new TreeNode(nums[mIdx]); root.left = toBST(nums, sIdx, mIdx - 1); root.right = toBST(nums, mIdx + 1, eIdx); return root; }
7. 根據有序鏈表構造平衡的二叉查找樹
109. Convert Sorted List to Binary Search Tree (Medium)
Given the sorted linked list: [-10,-3,0,5,9],
One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST:
0
/ \
-3 9
/ /
-10 5
public TreeNode sortedListToBST(ListNode head) { if (head == null) return null; if (head.next == null) return new TreeNode(head.val); ListNode preMid = preMid(head); ListNode mid = preMid.next; preMid.next = null; // 斷開鏈表 TreeNode t = new TreeNode(mid.val); t.left = sortedListToBST(head); t.right = sortedListToBST(mid.next); return t; } private ListNode preMid(ListNode head) { ListNode slow = head, fast = head.next; ListNode pre = head; while (fast != null && fast.next != null) { pre = slow; slow = slow.next; fast = fast.next.next; } return pre; }
8. 在二叉查找樹中尋找兩個節點,使它們的和為一個給定值
653. Two Sum IV - Input is a BST (Easy)
Input:
5
/ \
3 6
/ \ \
2 4 7
Target = 9
Output: True
使用中序遍歷得到有序數組之后,再利用雙指針對數組進行查找。
應該注意到,這一題不能用分別在左右子樹兩部分來處理這種思想,因為兩個待求的節點可能分別在左右子樹中。
public boolean findTarget(TreeNode root, int k) { List<Integer> nums = new ArrayList<>(); inOrder(root, nums); int i = 0, j = nums.size() - 1; while (i < j) { int sum = nums.get(i) + nums.get(j); if (sum == k) return true; if (sum < k) i++; else j--; } return false; } private void inOrder(TreeNode root, List<Integer> nums) { if (root == null) return; inOrder(root.left, nums); nums.add(root.val); inOrder(root.right, nums); }
9. 在二叉查找樹中查找兩個節點之差的最小絕對值
530. Minimum Absolute Difference in BST (Easy)
Input:
1
\
3
/
2
Output:
1
利用二叉查找樹的中序遍歷為有序的性質,計算中序遍歷中臨近的兩個節點之差的絕對值,取最小值。
private int minDiff = Integer.MAX_VALUE; private TreeNode preNode = null; public int getMinimumDifference(TreeNode root) { inOrder(root); return minDiff; } private void inOrder(TreeNode node) { if (node == null) return; inOrder(node.left); if (preNode != null) minDiff = Math.min(minDiff, node.val - preNode.val); preNode = node; inOrder(node.right); }
10. 尋找二叉查找樹中出現次數最多的值
501. Find Mode in Binary Search Tree (Easy)
1
\
2
/
2
return [2].
答案可能不止一個,也就是有多個值出現的次數一樣多。
private int curCnt = 1; private int maxCnt = 1; private TreeNode preNode = null; public int[] findMode(TreeNode root) { List<Integer> maxCntNums = new ArrayList<>(); inOrder(root, maxCntNums); int[] ret = new int[maxCntNums.size()]; int idx = 0; for (int num : maxCntNums) { ret[idx++] = num; } return ret; } private void inOrder(TreeNode node, List<Integer> nums) { if (node == null) return; inOrder(node.left, nums); if (preNode != null) { if (preNode.val == node.val) curCnt++; else curCnt = 1; } if (curCnt > maxCnt) { maxCnt = curCnt; nums.clear(); nums.add(node.val); } else if (curCnt == maxCnt) { nums.add(node.val); } preNode = node; inOrder(node.right, nums); }
Trie
Trie,又稱前綴樹或字典樹,用於判斷字符串是否存在或者是否具有某種字符串前綴。
1. 實現一個 Trie
208. Implement Trie (Prefix Tree) (Medium)
class Trie { private class Node { Node[] childs = new Node[26]; boolean isLeaf; } private Node root = new Node(); public Trie() { } public void insert(String word) { insert(word, root); } private void insert(String word, Node node) { if (node == null) return; if (word.length() == 0) { node.isLeaf = true; return; } int index = indexForChar(word.charAt(0)); if (node.childs[index] == null) { node.childs[index] = new Node(); } insert(word.substring(1), node.childs[index]); } public boolean search(String word) { return search(word, root); } private boolean search(String word, Node node) { if (node == null) return false; if (word.length() == 0) return node.isLeaf; int index = indexForChar(word.charAt(0)); return search(word.substring(1), node.childs[index]); } public boolean startsWith(String prefix) { return startWith(prefix, root); } private boolean startWith(String prefix, Node node) { if (node == null) return false; if (prefix.length() == 0) return true; int index = indexForChar(prefix.charAt(0)); return startWith(prefix.substring(1), node.childs[index]); } private int indexForChar(char c) { return c - 'a'; } }
2. 實現一個 Trie,用來求前綴和
Input: insert("apple", 3), Output: Null
Input: sum("ap"), Output: 3
Input: insert("app", 2), Output: Null
Input: sum("ap"), Output: 5
class MapSum { private class Node { Node[] child = new Node[26]; int value; } private Node root = new Node(); public MapSum() { } public void insert(String key, int val) { insert(key, root, val); } private void insert(String key, Node node, int val) { if (node == null) return; if (key.length() == 0) { node.value = val; return; } int index = indexForChar(key.charAt(0)); if (node.child[index] == null) { node.child[index] = new Node(); } insert(key.substring(1), node.child[index], val); } public int sum(String prefix) { return sum(prefix, root); } private int sum(String prefix, Node node) { if (node == null) return 0; if (prefix.length() != 0) { int index = indexForChar(prefix.charAt(0)); return sum(prefix.substring(1), node.child[index]); } int sum = node.value; for (Node child : node.child) { sum += sum(prefix, child); } return sum; } private int indexForChar(char c) { return c - 'a'; } }