CONTENT
leetcode 练习题目
144. 二叉树的前序遍历
94. 二叉树的中序遍历
145. 二叉树的后序遍历
二叉树定义
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;
}
}
1. 先序遍历
递归
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
dfs(root, res);
return res;
}
private void dfs(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
res.add(root.val);
dfs(root.left, res);
dfs(root.right, res);
}
}
迭代
直接做法
用栈模拟递归。先序遍历比较简单,访问的顺序和添加到结果的顺序是一致的,先序是:中左右,访问二叉树也是先访问中间节点,所以在访问的同时将当前节点加入结果列表中即可。
唯一需要注意的是,因为是用栈模拟的递归,所以若希望以 中左右 的顺序加入结果,需要先把右节点加入栈中,再把左节点接入栈中。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
if (root != null) {
stack.push(root);
}
while (!stack.isEmpty()) {
TreeNode curr = stack.pop();
res.add(curr.val);
if (curr.right != null) {
stack.push(curr.right);
}
if (curr.left != null) {
stack.push(curr.left);
}
}
return res;
}
}
标记法
先序遍历是:中左右。由于是栈,访问到某一个节点,所以先把右节点入栈,再把左节点入栈,并且不把当前节点加入结果,把访问过的当前节点再次放入栈中,但是在其后再 push 到栈中一个标记 null。如果遇到栈顶是 null ,就把栈顶加入结果。
这样放的意义在于,用栈来维护结果的顺序,带 null 的节点再出栈时,访问顺序一定是 中左右。如果当前节点加入栈中,在下一次循环访问栈顶的时候,中间节点带 null 必然会加入结果,然后是左节点作为中间节点,左节点的右节点和左节点入栈,再把左节点入栈再在其后 push 一个标记 null,然后再下一次循环左节点带 null 必然会加入结果,以此类推,左子树都结束之后,再才是右节点作为中间节点。所以可以得到 [中, [左子树], [右子树]]
这样的效果。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
if (root != null) {
stack.push(root);
}
while (!stack.isEmpty()) {
TreeNode curr = stack.pop();
if (curr != null) {
if (curr.right != null) {
stack.push(curr.right);
}
if (curr.left != null) {
stack.push(curr.left);
}
stack.push(curr);
stack.push(null);
} else {
curr = stack.pop();
res.add(curr.val);
}
}
return res;
}
}
2. 中序遍历
递归
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
dfs(root, res);
return res;
}
private void dfs(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
dfs(root.left, res);
res.add(root.val);
dfs(root.right, res);
}
}
迭代
直接做法
中序遍历是:左中右。所以需要先访问当前节点最左的节点。定义一个当前节点变量,使得它一直循环向左走直到变为 null,走到当前节点的最左,在走的过程中将路过的节点入栈记录下来。在走到最左后取栈顶,此时栈顶就是最左那个节点。将栈顶添加到结果(这相当于最左的中间节点),然后转向当前节点的右孩子继续(因为中间节点访问完,按顺序后面是右节点)
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
res.add(curr.val);
curr = curr.right;
}
return res;
}
}
标记法
中序遍历是:左中右。由于是栈,访问到某一个节点,所以先把右节点入栈,再中间节点,并在其后再 push 到栈中一个标记 null,再把左节点入栈,并且不把当前节点加入结果。之后如果遇到栈顶是 null ,就把栈顶加入结果。
这样放的意义在于,用栈来维护结果的顺序,带 null 的节点再出栈时,访问顺序一定是 [[左子树], 中, [右子树]]
。如果当前节点加入栈中,在下一次循环访问栈顶的时候,左节点作为中间节点,左节点的右节点入栈,再把左节点入栈再在其后 push 一个标记 null,再把左节点的左节点入栈,以此类推,如此左子树都结束之后,再才是中间节点,中间节点带 null 加入结果,然后是右节点作为中间节点。所以总体来看即 [[左子树], 中, [右子树]]
。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
if (root != null) {
stack.push(root);
}
while (!stack.isEmpty()) {
TreeNode curr = stack.pop();
if (curr != null) {
if (curr.right != null) {
stack.push(curr.right);
}
stack.push(curr);
stack.push(null);
if (curr.left != null) {
stack.push(curr.left);
}
} else {
curr = stack.pop();
res.add(curr.val);
}
}
return res;
}
}
3. 后序遍历
递归
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
dfs(root, res);
return res;
}
private void dfs(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
dfs(root.left, res);
dfs(root.right, res);
res.add(root.val);
}
}
迭代
直接做法
后序比较困难,因为后序遍历的顺序是:左右中,一直走左节点,走到头,还是不能访问中间节点,得把右节点再访问完,回来才能访问中间节点。那就需要判断,中间节点的右子树什么时候都访问完了?因为后序遍历的顺序是: [[左子树], [右子树]], 中
,如果中间节点的右节点是结果集的最后一个元素,那么说明它的右子树都访问过了,这时可以访问中间节点了。
所以可以记录上一次的输出。首先一直循环向左走直到为 null,走到最左,在走的过程中将节点入栈记录下来,走到 null 之后取栈顶,当前栈顶就是最左的节点,如果栈顶右孩子为 null,或者栈顶右孩子是上一次的输出,添加栈顶到结果集,否则还没到这个中间节点,还得把它入栈,然后向右孩子继续探索,直到它的右子树都访问完,即上一次的输出是它的右孩子。这样即能保证 [[左子树], [右子树]], 中
的顺序。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
TreeNode curr = root;
TreeNode pre = null;
while (curr != null || !stack.isEmpty()) {
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
if (curr.right == null || curr.right == pre) {
res.add(curr.val);
pre = curr;
curr = null;
} else {
stack.push(curr);
curr = curr.right;
}
}
return res;
}
}
标记法
后序遍历是:左右中。由于是栈,访问到某一个节点,所以先把中间节点入栈,并在其后再 push 到栈中一个标记 null,再把右节点入栈,再左节点入栈,并且不把当前节点加入结果。之后如果遇到栈顶是 null ,就把栈顶加入结果。
这样放的意义在于,用栈来维护结果的顺序,带 null 的节点再出栈时,访问顺序一定是 [[左子树], [右子树], 中]
。如果当前节点加入栈中,在下一次循环访问栈顶的时候,左节点作为中间节点,左节点入栈再在其后 push 一个标记 null,然后把左节点的右节点入栈,再把左节点的左节点入栈,以此类推,如此左子树都结束之后,再才是右节点作为中间节点,最后才是带 null 的中间节点,这时中间节点带 null 加入结果。所以总体来看即 [[左子树], [右子树], 中]
。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
if (root != null) {
stack.push(root);
}
while (!stack.isEmpty()) {
TreeNode curr = stack.pop();
if (curr != null) {
stack.push(curr);
stack.push(null);
if (curr.right != null) {
stack.push(curr.right);
}
if (curr.left != null) {
stack.push(curr.left);
}
} else {
curr = stack.pop();
res.add(curr.val);
}
}
return res;
}
}
反转类前序遍历结果
前序遍历的顺序是:[中, [左子树], [右子树]]
,后序遍历是:[[左子树], [右子树], 中]
,将容易写的先序遍历的直接做法的写法改成 [中, [右子树], [左子树]]
,得到这样的类前序遍历的结果之后,最后再反转结果即是后序遍历的结果。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> stack = new LinkedList<>();
if (root != null) {
stack.push(root);
}
while (!stack.isEmpty()) {
TreeNode curr = stack.pop();
res.add(curr.val);
if (curr.left != null) {
stack.push(curr.left);
}
if (curr.right != null) {
stack.push(curr.right);
}
}
Collections.reverse(res);
return res;
}
}