二叉樹
- 簡介
- [簡單] 144. 二叉樹的前序遍歷、94. 二叉樹的中序遍歷、145. 二叉樹的后序遍歷
- 二叉樹層序遍歷
- [中等] 102. 二叉樹的層序遍歷
- [中等] 107. 二叉樹的層序遍歷 II
- [中等] 199. 二叉樹的右視圖
- [簡單] 637. 二叉樹的層平均值
- [中等] 429. N 叉樹的層序遍歷
- [中等] 515. 在每個樹行中找最大值
- [中等] 116. 填充每個節點的下一個右側節點指針、[中等]117. 填充每個節點的下一個右側節點指針 II
- [簡單] 104. 二叉樹的最大深度
- [簡單] 111. 二叉樹的最小深度
- [簡單] 226. 翻轉二叉樹
- [簡單] 101. 對稱二叉樹
- [簡單] 100. 相同的樹
- [簡單] 572. 另一棵樹的子樹
- [簡單] 222. 完全二叉樹的節點個數
- [簡單] 110. 平衡二叉樹
- [簡單] 257. 二叉樹的所有路徑
簡介
記錄一下自己刷題的歷程以及代碼。寫題過程中參考了 代碼隨想錄的刷題路線。會附上一些個人的思路,如果有錯誤,可以在評論區提醒一下。
涉及:二叉樹前中后序遍歷、層序遍歷、隊列Queue、頭插法、遞歸、ArrayList、LinkedList、遞歸、StringBuilder
[簡單] 144. 二叉樹的前序遍歷、94. 二叉樹的中序遍歷、145. 二叉樹的后序遍歷
144. 二叉樹的前序遍歷
94. 二叉樹的中序遍歷
145. 二叉樹的后序遍歷
前中后序遍歷可以公用一套遞歸思路,都是比較經典的模板:
//先序遍歷
遞歸訪問(){對節點做操作遞歸訪問(左子樹)遞歸訪問(右子樹)
}//中序遍歷
遞歸訪問(){遞歸訪問(左子樹)對節點做操作遞歸訪問(右子樹)
}//后序遍歷
遞歸訪問(){遞歸訪問(左子樹)遞歸訪問(右子樹)對節點做操作
}
- 二叉樹的前序遍歷
class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> answer = new ArrayList<>();preorder(root, answer);return answer;}public void preorder(TreeNode root, List<Integer> answer){if(root == null) return;answer.add(root.val);preorder(root.left,answer);preorder(root.right,answer);return;}
}
- 二叉樹的中序遍歷
class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> answer = new ArrayList<>();inorder(root, answer);return answer;}public void inorder(TreeNode root, List<Integer> answer){if(root == null) return;inorder(root.left,answer);answer.add(root.val);inorder(root.right,answer);return;}
}
- 二叉樹的后序遍歷
class Solution {public List<Integer> postorderTraversal(TreeNode root) {List<Integer> answer = new ArrayList<>();postorder(root, answer);return answer;}public void postorder(TreeNode root, List<Integer> answer){if(root == null) return;postorder(root.left,answer);postorder(root.right,answer);answer.add(root.val);return;}
}
二叉樹層序遍歷
[中等] 102. 二叉樹的層序遍歷
原題鏈接
經典的BFS
用隊列保存樹節點,每次統計隊列的size(),也就是第n層節點數量。
處理這一層的節點,將其子節點全部加入隊列,循環往復到隊列為空。
class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> ans = new ArrayList<List<Integer>>();Queue<TreeNode> queue = new ArrayDeque<>();if(root == null) return ans;queue.add(root);while(!queue.isEmpty()){List<Integer> nums = new ArrayList<Integer>();int k = queue.size();while(k-- > 0) {TreeNode temp = queue.remove();nums.add(temp.val);if (temp.left != null) queue.add(temp.left);if (temp.right != null) queue.add(temp.right);}ans.add(nums);}return ans;}
}
[中等] 107. 二叉樹的層序遍歷 II
原題鏈接
方法①:
與102的最基本的層序遍歷相似的邏輯,使用遞歸的方法把每次ans.add(nums);
操作順序進行了倒序。
class Solution {public List<List<Integer>> levelOrderBottom(TreeNode root) {List<List<Integer>> ans = new ArrayList<List<Integer>>();Queue<TreeNode> queue = new ArrayDeque<>();if(root == null) return ans;queue.add(root);recursion(queue, ans);return ans;}public void recursion(Queue<TreeNode> queue, List<List<Integer>> ans){if(!queue.isEmpty()){List<Integer> nums = new ArrayList<Integer>();int k = queue.size();while(k-- > 0) {TreeNode temp = queue.remove();nums.add(temp.val);if (temp.left != null) queue.add(temp.left);if (temp.right != null) queue.add(temp.right);}recursion(queue, ans);ans.add(nums);}return;}
}
方法②:
使用LinkedList
,用頭插法的方式構造返回值。(LinkedList
底層為鏈表,頭插法比較方便,ArrayList
底層是連續存儲,頭插法復雜度為O(n))
class Solution {public List<List<Integer>> levelOrderBottom(TreeNode root) {List<List<Integer>> ans = new LinkedList<>();Queue<TreeNode> queue = new ArrayDeque<>();if(root == null) return ans;queue.add(root);while(!queue.isEmpty()){List<Integer> nums = new ArrayList<Integer>();int k = queue.size();while(k-- > 0) {TreeNode temp = queue.remove();nums.add(temp.val);if (temp.left != null) queue.add(temp.left);if (temp.right != null) queue.add(temp.right);}ans.add(0, nums);}return ans;}}
[中等] 199. 二叉樹的右視圖
原題鏈接
與102的最基本的層序遍歷相似的邏輯,構建返回值時每次只把當前層最右邊的數加入ans即可。
class Solution {public List<Integer> rightSideView(TreeNode root) {List<Integer> ans = new ArrayList<Integer>();Queue<TreeNode> queue = new ArrayDeque<>();if(root == null) return ans;queue.add(root);while(!queue.isEmpty()){List<Integer> nums = new ArrayList<Integer>();int k = queue.size();while(k-- > 0) {TreeNode temp = queue.remove();nums.add(temp.val);if (temp.left != null) queue.add(temp.left);if (temp.right != null) queue.add(temp.right);}ans.add((nums.get(nums.size()-1)));}return ans;}
}
[簡單] 637. 二叉樹的層平均值
原題鏈接
class Solution {public List<Double> averageOfLevels(TreeNode root) {Queue<TreeNode> queue = new ArrayDeque<>();List<Double> ans = new ArrayList<>();if(root == null) return ans;queue.add(root);while(!queue.isEmpty()){double num = 0;int k = queue.size();int i = k;while(k-- > 0){TreeNode temp = queue.remove();num += temp.val;if(temp.left != null) queue.add(temp.left);if(temp.right != null) queue.add(temp.right);}ans.add(num / i);}return ans;}
}
[中等] 429. N 叉樹的層序遍歷
原題鏈接
class Solution {public List<List<Integer>> levelOrder(Node root) {List<List<Integer>> ans = new ArrayList<List<Integer>>();Queue<Node> queue = new ArrayDeque<>();if(root == null) return ans;queue.add(root);while(!queue.isEmpty()){List<Integer> nums = new ArrayList<Integer>();int k = queue.size();while(k-- > 0) {Node temp = queue.remove();nums.add(temp.val);for(int i = 0; i < temp.children.size(); i++) {queue.add(temp.children.get(i));}}ans.add(nums);}return ans;}
}
[中等] 515. 在每個樹行中找最大值
原題鏈接
class Solution {public List<Integer> largestValues(TreeNode root) {List<Integer> ans = new ArrayList<>();Queue<TreeNode> queue = new ArrayDeque<>();if(root == null) return ans;queue.add(root);while(!queue.isEmpty()){int max = queue.peek().val;int k = queue.size();while(k-- > 0) {TreeNode temp = queue.remove();max = max > temp.val ? max : temp.val;if(temp.left != null) queue.add(temp.left);if(temp.right != null) queue.add(temp.right);}ans.add(max);}return ans;}
}
[中等] 116. 填充每個節點的下一個右側節點指針、[中等]117. 填充每個節點的下一個右側節點指針 II
[中等] 116. 填充每個節點的下一個右側節點指針
[中等]117. 填充每個節點的下一個右側節點指針 II
方法①:
正常的層序遍歷,每層除了最后一個節點之外都對next賦值
class Solution {public Node connect(Node root) {List<Integer> ans = new ArrayList<>();Queue<Node> queue = new ArrayDeque<>();if(root == null) return root;queue.add(root);while(!queue.isEmpty()){int k = queue.size();while(k-- > 0) {Node temp = queue.remove();if(k > 0) {temp.next = queue.peek();}if(temp.left != null) queue.add(temp.left);if(temp.right != null) queue.add(temp.right);}}return root;}
}
方法②:
使用next鏈接來對同層次節點做遍歷,可以省去隊列的開銷
注意Node引用需要申請在方法外,不能做參數傳遞,java中都是值傳遞
class Solution {Node last = null, nextStart = null;public Node connect(Node root) {List<Integer> ans = new ArrayList<>();if(root == null) return root;Node p = root;while(p!=null){if(p.left != null){handle(p.left);}if(p.right != null){handle(p.right);}p = p.next;if(p == null && nextStart != null){p = nextStart;nextStart = null;last = null;}}return root;}public void handle(Node p){if(nextStart == null){nextStart = p;}if(last != null){last.next = p;}last = p;}}
[簡單] 104. 二叉樹的最大深度
原題鏈接
方法①:層序遍歷
class Solution {public int maxDepth(TreeNode root) {int depth = 0;if(root == null) return depth;Queue<TreeNode> queue = new ArrayDeque<>();queue.add(root);while(!queue.isEmpty()){depth++;int k = queue.size();while(k-- > 0) {TreeNode temp = queue.remove();if (temp.left != null) queue.add(temp.left);if (temp.right != null) queue.add(temp.right);}}return depth;}
}
方法②:遞歸
樹的高度就是 其子樹的最大高度 + 1,用在多叉樹上也是一樣的思路
class Solution {public int maxDepth(TreeNode root) {if(root == null) return 0;int leftDepth = maxDepth(root.left);int rightDepth = maxDepth(root.right);return (leftDepth > rightDepth ? leftDepth : rightDepth) + 1;}
}
[簡單] 111. 二叉樹的最小深度
原題鏈接
方法①:層序遍歷找左右子樹皆空的點即可
class Solution {public int minDepth(TreeNode root) {int depth = 0;if(root == null) return depth;Queue<TreeNode> queue = new ArrayDeque<>();queue.add(root);while(!queue.isEmpty()){depth++;int k = queue.size();while(k-- > 0) {TreeNode temp = queue.remove();if(temp.left == null && temp.right == null) return depth;if (temp.left != null) queue.add(temp.left);if (temp.right != null) queue.add(temp.right);}}return depth;}
}
方法②:遞歸求解
用遞歸求解記住需要的是到葉子節點的深度
如果非葉子節點,假設只有單邊左子樹,右子數應當是找不到葉子節點也就是距離無窮大,可以設置一個Integer.MAX_VALUE
做為返回值,這樣通過比較,遞歸的上一層就會獲得左子樹找到葉子節點的最小距離 + 1
class Solution {public int minDepth(TreeNode root) {if(root == null) return 0;if(root.left == null && root.right == null) return 1;int leftMin = Integer.MAX_VALUE;int rightMin = Integer.MAX_VALUE;if(root.left != null) leftMin = minDepth(root.left);if(root.right != null) rightMin = minDepth(root.right);return (leftMin < rightMin ? leftMin : rightMin) + 1;}
}
[簡單] 226. 翻轉二叉樹
原題鏈接
前序遍歷的基礎上每一次遍歷節點做翻轉操作。
切記前序、后續、層次遍歷都可以,但是不可以是中序遍歷,因為中序遍歷是左 中 右
的順序,遞歸調整左子樹之后,處理當前節點會把左右子樹對調,這樣進入右子數遞歸時其實還是對原先的左子樹做操作。
class Solution {public TreeNode invertTree(TreeNode root) {preorder(root);return root;}public void preorder(TreeNode root){if(root == null) return;TreeNode temp = root.left;root.left = root.right;root.right = temp;preorder(root.left);preorder(root.right);return;}
}
[簡單] 101. 對稱二叉樹
原題鏈接
經典的遞歸思路,對左右子樹做反方向的遞歸即可,在左子樹上做前序遍歷,每次遞歸left的左節點時就去遞歸right的右節點,遞歸left的右節點時則遞歸right的左節點。
class Solution {public boolean isSymmetric(TreeNode root) {if(root == null) return true;TreeNode left = root.left;TreeNode right = root.right;return recursion(left, right);}public boolean recursion(TreeNode left, TreeNode right){if(left == null && right == null)return true;else if(left != null && right != null) {if (left.val != right.val)return false;if (recursion(left.left, right.right) && recursion(left.right, right.left))return true;}return false;}
}
[簡單] 100. 相同的樹
原題鏈接
兩棵樹同頻做前序遍歷即可,其他遍歷方式也是ok的。
class Solution {public boolean isSameTree(TreeNode p, TreeNode q) {return recursion(p, q);}public boolean recursion(TreeNode p, TreeNode q){if(p == null && q == null)return true;else if(p != null && q != null) {if (p.val != q.val)return false;if (recursion(p.left, q.left) && recursion(p.right, q.right))return true;}return false;}
}
[簡單] 572. 另一棵樹的子樹
原題鏈接
兩層遞歸
①preorder:對root 的前序遍歷,找到與subRoot相同值的節點,作為比較的起點②cmprecursion:對root的節點以及subRoot的根節點 做 同頻前序遍歷對比
class Solution {public boolean isSubtree(TreeNode root, TreeNode subRoot) {if(subRoot == null) return true;if(root == null) return true;return preorder(root, subRoot);}public boolean preorder(TreeNode p, TreeNode q){if(p == null) return false;if(p.val == q.val && cmprecursion(p, q))return true;if(preorder(p.left, q) || preorder(p.right, q)) return true;return false;}public boolean cmprecursion(TreeNode p, TreeNode q){if(p == null && q == null) return true;else if(p != null && q != null){if(p.val != q.val) return false;if(cmprecursion(p.left, q.left) && cmprecursion(p.right, q.right))return true;}return false;}
}
[簡單] 222. 完全二叉樹的節點個數
原題鏈接
遞歸
class Solution {public int countNodes(TreeNode root) {if(root == null) return 0;return countNodes(root.left) + countNodes(root.right) + 1;}
}
[簡單] 110. 平衡二叉樹
原題鏈接
遞歸,后序遍歷
用-2標記 以當前節點為根節點的子樹非平衡二叉樹,在遞歸中一旦出現-2就層層傳遞到root,用來標識存在子樹為非平衡二叉樹
class Solution {public boolean isBalanced(TreeNode root) {if(countDepth(root) == -2) return false;return true;}public int countDepth(TreeNode p){if(p == null) return 0;int leftDepth = countDepth(p.left);int rightDepth = countDepth(p.right);int flag = leftDepth - rightDepth;if(leftDepth == -2 || rightDepth == -2 || flag > 1 || flag < -1) return -2;else return (leftDepth > rightDepth ? leftDepth : rightDepth) + 1;}
}
[簡單] 257. 二叉樹的所有路徑
原題鏈接
思路就是DFS深度優先遍歷找到每一條路徑,效率差異主要體現在對字符串拼接的處理上,使用StringBuilder會更高效一些。
class Solution {public List<String> binaryTreePaths(TreeNode root) {List<String> ans = new ArrayList<>();if(root == null) return ans;DFS(root, ans, "");return ans;}public void DFS(TreeNode p, List<String> ans, String string){StringBuilder sb = new StringBuilder(string);sb.append(p.val);if(p.left == null && p.right == null){ans.add(sb.toString());return;}sb.append("->");if(p.left != null)DFS(p.left, ans, sb.toString());if(p.right != null)DFS(p.right, ans, sb.toString());}
}