LeetCode 6. 二叉樹(一)
目錄刷題順序來自:程式碼隨想錄
二叉樹的遞迴遍歷
144. 二叉樹的前序遍歷
給你二叉樹的根節點 root
,返回它節點值的 前序 遍歷。
public List<Integer> preorderTraversal(TreeNode root) { ArrayList<Integer> list = new ArrayList<>(); preOrder(list, root); return list; } private void preOrder(ArrayList<Integer> list, TreeNode root) { if(root!=null) { list.add(root.val); preOrder(list, root.left); preOrder(list, root.right); } }
145. 二叉樹的後序遍歷
public List<Integer> postorderTraversal(TreeNode root) { ArrayList<Integer> list = new ArrayList<>(); postOrder(list, root); return list; } private void postOrder(ArrayList<Integer> list, TreeNode root) { if(root != null) { postOrder(list, root.left); postOrder(list, root.right); list.add(root.val); } }
94. 二叉樹的中序遍歷
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList<Integer> list = new ArrayList<>();
inOrder(list, root);
return list;
}
private void inOrder(ArrayList<Integer> list, TreeNode root) {
if(root != null) {
inOrder(list, root.left);
list.add(root.val);
inOrder(list, root.right);
}
}
二叉樹的迭代遍歷
144. 二叉樹的前序遍歷
彈出棧頂元素,輸出棧頂的值,壓入右節點、左節點。
public List<Integer> preorderTraversal(TreeNode root) {
ArrayList<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if(root != null) {
stack.push(root);
}
while(!stack.empty()) {
TreeNode node = stack.pop();
if(node.right != null) {
stack.push(node.right);
}
if(node.left != null) {
stack.push(node.left);
}
list.add(node.val);
}
return list;
}
可以採取統一風格的寫法,其主要思想是:
- 當棧頂元素沒有被訪問過,將棧頂元素彈出,按倒序壓入(例如前序,那就按右左中的順序壓入),並且在壓入中節點(當前節點)時,在其後壓入
null
表示該節點已經被訪問過了 - 當棧頂元素為
null
,表示該節點被訪問過,連續彈出2次並輸出值
public List<Integer> preorderTraversal(TreeNode root) {
ArrayList<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if(root != null) {
stack.push(root);
}
while(!stack.empty()) {
if(stack.peek() != null) {
TreeNode node = stack.pop();
if(node.right != null) {
stack.push(node.right);
}
if(node.left != null) {
stack.push(node.left);
}
stack.push(node);
stack.push(null);
}
else {
stack.pop();
list.add(stack.pop().val);
}
}
return list;
}
145. 二叉樹的後序遍歷
彈出節點棧頂元素,壓入左節點、右節點,將棧頂元素壓入輸出棧。依次彈出輸出棧的值。
public List<Integer> postorderTraversal(TreeNode root) {
ArrayList<Integer> list = new ArrayList<>();
Stack<TreeNode> nodeStack = new Stack<>();
Stack<Integer> outputStack = new Stack<>();
if(root != null) {
nodeStack.push(root);
}
while(!nodeStack.empty()) {
TreeNode node = nodeStack.pop();
if(node.left != null) {
nodeStack.push(node.left);
}
if(node.right != null) {
nodeStack.push(node.right);
}
outputStack.push(node.val);
}
while(!outputStack.empty()) {
list.add(outputStack.pop());
}
return list;
}
94. 二叉樹的中序遍歷
一直向棧中壓左節點直到當前節點為空,彈出棧頂節點,壓右節點,重複。
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if(root != null) {
stack.push(root);
}
TreeNode curr = root;
while(!stack.empty()) {
if(curr != null) {
curr = curr.left;
}
else {
curr = stack.pop();
list.add(curr.val);
curr = curr.right;
}
if(curr != null) {
stack.push(curr);
}
}
return list;
}
二叉樹層序遍歷
102. 二叉樹的層序遍歷
給你一個二叉樹,請你返回其按 層序遍歷 得到的節點值。 (即逐層地,從左到右訪問所有節點)。
主要思想是通過FIFO的佇列遍歷。
public List<List<Integer>> levelOrder(TreeNode root) {
ArrayList<List<Integer>> res = new ArrayList<>();
LinkedList<TreeNode> currLayer = new LinkedList<>();
if(root != null) {
currLayer.add(root);
}
// 每次迴圈遍歷一層
while(currLayer.size() != 0) {
currLayer.add(null); // 在當前層最後加一個null的標誌
ArrayList<Integer> list = new ArrayList<>(); // 儲存當前層遍歷的結果
// 除了加null標誌,還可以控制迴圈次數等於currLayer.size()
while(currLayer.getFirst() != null) {
TreeNode node = currLayer.removeFirst();
list.add(node.val);
// 左右節點新增到隊尾(與當前層有null標誌隔開)
if(node.left != null) {
currLayer.add(node.left);
}
if(node.right != null) {
currLayer.add(node.right);
}
}
currLayer.removeFirst(); // 移除之前新增的null標誌
res.add(list); // 儲存當前層的遍歷結果
}
return res;
}
通過遞迴的方式遍歷。
public List<List<Integer>> levelOrder(TreeNode root) {
ArrayList<List<Integer>> res = new ArrayList<>();
level(res, root, 1);
return res;
}
private void level(List<List<Integer>> res, TreeNode node, int deep) {
if(node == null) return;
// 當前節點是該層第一個節點,需要例項化一個數組
if(res.size() < deep) {
res.add(new ArrayList<Integer>());
}
res.get(deep-1).add(node.val);
level(res, node.left, deep+1);
level(res, node.right, deep+1);
}
107. 二叉樹的層序遍歷 II
給定一個二叉樹,返回其節點值自底向上的層序遍歷。 (即按從葉子節點所在層到根節點所在的層,逐層從左向右遍歷)。
和上一題類似,只需要把輸出結果在棧中反轉即可,或者直接呼叫Collections.reverse()
方法。
public List<List<Integer>> levelOrderBottom(TreeNode root) {
ArrayList<List<Integer>> res = new ArrayList<>();
LinkedList<TreeNode> currLayer = new LinkedList<>();
if(root == null) {
return res;
}
else {
currLayer.add(root);
}
Stack<List<Integer>> outputStack = new Stack<>(); //
while(currLayer.size() != 0) {
int count = currLayer.size();
ArrayList<Integer> list = new ArrayList<>();
for(int i = 0; i < count; i++) {
TreeNode node = currLayer.removeFirst();
list.add(node.val);
if(node.left != null) {
currLayer.add(node.left);
}
if(node.right != null) {
currLayer.add(node.right);
}
}
outputStack.push(list); // 先將輸出結果放入棧
}
// 輸出結果反轉
while(!outputStack.empty()) {
res.add(outputStack.pop());
}
return res;
}
199. 二叉樹的右檢視
給定一個二叉樹的 根節點 root
,想象自己站在它的右側,按照從頂部到底部的順序,返回從右側所能看到的節點值。
public List<Integer> rightSideView(TreeNode root) {
ArrayList<Integer> res = new ArrayList<>();
LinkedList<TreeNode> currLayer = new LinkedList<>();
if(root == null) {
return res;
}
else {
currLayer.add(root);
}
while(currLayer.size() > 0) {
int len = currLayer.size();
for(int i = 0; i < len; i++) {
TreeNode node = currLayer.removeFirst();
if(node.left != null) {
currLayer.add(node.left);
}
if(node.right != null) {
currLayer.add(node.right);
}
// 新增最右側節點的值
if(i == len - 1) {
res.add(node.val);
}
}
}
return res;
}
637. 二叉樹的層平均值
給定一個非空二叉樹, 返回一個由每層節點平均值組成的陣列。
public List<Double> averageOfLevels(TreeNode root) {
ArrayList<Double> res = new ArrayList<>();
LinkedList<TreeNode> currLayer = new LinkedList<>();
if(root != null) {
currLayer.add(root);
}
while(currLayer.size() != 0) {
int len = currLayer.size();
double sum = 0; // 當前層累加和
for(int i = 0; i < len; i++) {
TreeNode node = currLayer.removeFirst();
if(node.left != null) {
currLayer.add(node.left);
}
if(node.right != null) {
currLayer.add(node.right);
}
sum += node.val;
}
res.add(sum / len);
}
return res;
}
429. N 叉樹的層序遍歷
給定一個 N 叉樹,返回其節點值的層序遍歷。(即從左到右,逐層遍歷)。
public List<List<Integer>> levelOrder(Node root) {
ArrayList<List<Integer>> res = new ArrayList<>();
LinkedList<Node> currLayer = new LinkedList<>();
if(root != null) {
currLayer.add(root);
}
while(currLayer.size() != 0) {
int len = currLayer.size();
ArrayList<Integer> list = new ArrayList<>();
for(int i = 0; i < len; i++) {
Node node = currLayer.removeFirst();
list.add(node.val);
// 將孩子節點依次加入佇列
List<Node> children = node.children;
if(children != null) {
for(Node child: children) {
currLayer.add(child);
}
}
}
res.add(list);
}
return res;
}
515. 在每個樹行中找最大值
給定一棵二叉樹的根節點 root
,請找出該二叉樹中每一層的最大值。
public List<Integer> largestValues(TreeNode root) {
ArrayList<Integer> res = new ArrayList<Integer>();
LinkedList<TreeNode> currLayer = new LinkedList<>();
if(root != null) {
currLayer.add(root);
}
while(currLayer.size() != 0) {
int len = currLayer.size();
int max = currLayer.peekFirst().val;
for(int i = 0; i < len; i++) {
TreeNode node = currLayer.removeFirst();
if(node.left != null) {
currLayer.add(node.left);
}
if(node.right != null) {
currLayer.add(node.right);
}
if(max < node.val) {
max = node.val;
}
}
res.add(max);
}
return res;
}
116. 填充每個節點的下一個右側節點指標
給定一個 完美二叉樹 ,其所有葉子節點都在同一層,每個父節點都有兩個子節點。二叉樹定義如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每個 next 指標,讓這個指標指向其下一個右側節點。如果找不到下一個右側節點,則將 next 指標設定為 NULL
。
public Node connect(Node root) {
LinkedList<Node> currLayer = new LinkedList<>();
if(root != null) {
currLayer.add(root);
}
while(currLayer.size() != 0) {
int len = currLayer.size();
for(int i = 0; i < len; i++) {
Node node = currLayer.removeFirst();
if(node.left != null) {
currLayer.add(node.left);
}
if(node.right != null) {
currLayer.add(node.right);
}
if(i != len - 1) {
node.next = currLayer.peekFirst();
}
}
}
return root;
}
也可以用遞迴的方法。
public Node connect(Node root) {
if(root == null || root.left == null) {
return root;
}
root.left.next = root.right;
if(root.next != null) {
root.right.next = root.next.left;
}
connect(root.left);
connect(root.right);
return root;
}
117. 填充每個節點的下一個右側節點指標 II
給定一個二叉樹。二叉樹定義如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每個 next 指標,讓這個指標指向其下一個右側節點。如果找不到下一個右側節點,則將 next 指標設定為 NULL
。
與上一題第一個層級遍歷的程式碼相同。
翻轉二叉樹
226. 翻轉二叉樹
翻轉一棵二叉樹。
遞迴
public TreeNode invertTree(TreeNode root) {
if(root == null) {
return root;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
invertTree(root.left);
invertTree(root.right);
return root;
}
廣度優先
使用FIFO佇列層級遍歷,每次遍歷時翻轉節點的左右子樹。
深度優先
使用棧遍歷,每次遍歷時翻轉節點的左右子樹。
對稱二叉樹
101. 對稱二叉樹
給定一個二叉樹,檢查它是否是映象對稱的。
public boolean isSymmetric(TreeNode root) {
if(root == null) {
return true;
}
else {
return symmetric(root.left, root.right);
}
}
// 比較tree1和tree2是否是對稱的
private boolean symmetric(TreeNode tree1, TreeNode tree2) {
if(tree1 == null && tree2 == null) { // 都為null
return true;
}
if(tree1 == null || tree2 == null) { // 其中有一個為null
return false;
}
// 比較根節點的值
// 如果相同,則比較tree1的左子樹與tree2的右子樹,tree1的右子樹與tree2的左子樹
return tree1.val == tree2.val && symmetric(tree1.left, tree2.right) && symmetric(tree1.right, tree2.left);
}
100. 相同的樹
給你兩棵二叉樹的根節點 p
和 q
,編寫一個函式來檢驗這兩棵樹是否相同。
與上一題類似的遞迴演算法。
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);
}
572. 另一棵樹的子樹
給你兩棵二叉樹 root
和 subRoot
。檢驗 root
中是否包含和 subRoot
具有相同結構和節點值的子樹。如果存在,返回 true
;否則,返回 false
。
二叉樹 tree
的一棵子樹包括 tree
的某個節點和這個節點的所有後代節點。tree
也可以看做它自身的一棵子樹。
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(isSameTree(root, subRoot)) {
return true;
}
if(root == null) {
return false;
}
// 左子樹和右子樹之間有一個存在該子樹即可
return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}
// 判斷tree1與tree2是否相等
private boolean isSameTree(TreeNode tree1, TreeNode tree2) {
if(tree1 == null && tree2 == null) {
return true;
}
if(tree1 == null || tree2 == null) {
return false;
}
return tree1.val == tree2.val && isSameTree(tree1.left, tree2.left) && isSameTree(tree1.right, tree2.right);
}
二叉樹的深度
104. 二叉樹的最大深度
給定一個二叉樹,找出其最大深度。
使用遞迴方法,也可以用佇列層級遍歷。
public int maxDepth(TreeNode root) {
return depth(root, 0);
}
private int depth(TreeNode root, int deep) {
if(root == null) {
return deep;
}
else {
return Math.max(depth(root.left, deep + 1), depth(root.right, deep + 1));
}
}
559. N 叉樹的最大深度
給定一個 N 叉樹,找到其最大深度。
遞迴方法:
public int maxDepth(Node root) {
if(root == null) {
return 0;
}
// 最大深度等於所有兒子節點中的最大深度+1
int max = 0;
for(Node child: root.children) {
int depth = maxDepth(child);
if(depth > max) {
max = depth;
}
}
return max + 1;
}
111. 二叉樹的最小深度
給定一個二叉樹,找出其最小深度。
最小深度是從根節點到最近葉子節點的最短路徑上的節點數量。
遞迴
public int minDepth(TreeNode root) {
if(root == null) {
return 0;
}
if(root.left == null && root.right == null) {
return 1;
}
// 左右子樹都不為null,則最小深度位較小子樹深度+1
if(root.left != null && root.right != null) {
return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
}
// 左右子樹有一個為null,則最小深度為不為空子樹的深度+1
if(root.left == null) {
return minDepth(root.right) + 1;
}
else {
return minDepth(root.left) + 1;
}
}
一種更簡潔的寫法:
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;
}
else {
return Math.min(left, right) + 1;
}
}
迭代
使用佇列層級遍歷,當遇到第一個葉子節點時返回。
public int minDepth(TreeNode root) {
if(root == null) {
return 0;
}
LinkedList<TreeNode> currLayer = new LinkedList<>();
currLayer.add(root);
int depth = 0;
while(currLayer.size() != 0) {
depth++;
int len = currLayer.size();
for(int i = 0; i < len; i++) {
TreeNode node = currLayer.removeFirst();
if(node.left == null && node.right == null) { // 遇到葉子節點
currLayer.clear(); // 觸發while迴圈終止條件
break;
}
if(node.left != null) currLayer.add(node.left);
if(node.right != null) currLayer.add(node.right);
}
}
return depth;
}
完全二叉樹節點個數
222. 完全二叉樹的節點個數
給你一棵 完全二叉樹 的根節點 root
,求出該樹的節點個數。
完全二叉樹 的定義如下:在完全二叉樹中,除了最底層節點可能沒填滿外,其餘每層節點數都達到最大值,並且最下面一層的節點都集中在該層最左邊的若干位置。若最底層為第 h
層,則該層包含 1~ 2h
個節點。
遞迴
節點個數=左子樹個數+右子樹個數+1。
public int countNodes(TreeNode root) {
if(root == null) return 0;
int left = countNodes(root.left);
int right = countNodes(root.right);
return left + right + 1;
}
迭代
可以繼續用層級遍歷的方法,但是效率太低(4ms)。可以先計算二叉樹的深度(一直搜尋最左邊的節點),然後計算最後一層節點的個數,因為除了最後一層, 之前層都是滿節點的,這樣效率更高(0ms)。
public int countNodes(TreeNode root) {
if(root == null) return 0;
int depth = 1;
TreeNode node = root;
while(node.left != null) { // 計算深度
depth++;
node = node.left;
}
ArrayList<TreeNode> list = new ArrayList<>();
lastLayer(list, root, depth);
// 除了最後一層的節點數量+最後一層節點數量
return (int) Math.pow(2, depth - 1) - 1 + list.size();
}
// 將最後一層節點加入list
private void lastLayer(ArrayList<TreeNode> list, TreeNode root, int depth) {
if(root == null) return;
if(depth == 1) {
list.add(root);
}
else {
lastLayer(list, root.left, depth-1);
lastLayer(list, root.right, depth-1);
}
}
平衡二叉樹
110. 平衡二叉樹
給定一個二叉樹,判斷它是否是高度平衡的二叉樹。本題中,一棵高度平衡二叉樹定義為:一個二叉樹每個節點 的左右兩個子樹的高度差的絕對值不超過 1 。
自頂向下遞迴
如果一個二叉樹左子樹和右子樹的高度差不大於1,且左子樹、右子樹都是是平衡的,則這個二叉樹是平衡的。這種做法效率較低,因為節點的高度被重複計算了很多次。
// 左子樹、右子樹高度差<=1,且左子樹、右子樹都是平衡的
public boolean isBalanced(TreeNode root) {
if(root == null) return true;
int left = getHeight(root.left);
int right = getHeight(root.right);
return Math.abs(left-right) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
// 左子樹和右子樹較大的高度+1
private int getHeight(TreeNode root) {
if(root == null) return 0;
int left = getHeight(root.left);
int right = getHeight(root.right);
return Math.max(left, right) + 1;
}
遞迴優化
對上面的演算法做一些剪枝操作,避免重複計算,使用-1標記當前子樹已經不平衡了,如果不平衡,則沒有必要進行計算高度了。
public boolean isBalanced(TreeNode root) {
return getHeight(root) != -1;
}
private int getHeight(TreeNode root) {
if(root == null) return 0;
// 左子樹或右子樹已經不平衡了,那麼當前樹也不平衡
int left = getHeight(root.left);
if(left == -1) return -1;
int right = getHeight(root.right);
if(right == -1) return -1;
if(Math.abs(left - right) > 1) return -1; // 左子樹、右子樹高度差超過1,當前樹不平衡
return Math.max(left, right) + 1; // 如果是平衡的,返回高度
}
二叉樹的所有路徑
257. 二叉樹的所有路徑
給你一個二叉樹的根節點 root
,按 任意順序 ,返回所有從根節點到葉子節點的路徑。葉子節點 是指沒有子節點的節點。
遞迴
public List<String> binaryTreePaths(TreeNode root) {
ArrayList<String> list = new ArrayList<>();
getPath(root, list, "");
return list;
}
private void getPath(TreeNode root, List<String> list, String path) {
if(root == null) return;
// 將當前節點新增到路徑中
path += root.val;
if(root.left == null && root.right == null) { // 走到葉子節點返回
list.add(path);
}
else { // 繼續往左右子樹探索
path += "->";
getPath(root.left, list, path);
getPath(root.right, list, path);
}
}
優化,使用可變字串StringBuilder
,此時要注意需要深拷貝。
public List<String> binaryTreePaths(TreeNode root) {
ArrayList<String> list = new ArrayList<>();
getPath(root, list, new StringBuilder(""));
return list;
}
private void getPath(TreeNode root, List<String> list, StringBuilder path) {
if(root == null) return;
path.append(root.val);
if(root.left == null && root.right == null) {
list.add(path.toString());
}
else {
path.append("->");
getPath(root.left, list, new StringBuilder(path.toString()));
getPath(root.right, list, new StringBuilder(path.toString()));
}
}