今天写二叉树的三种遍历,分别用递归,迭代和Morris遍历。按理来说是9种,但是后序遍历用Morris遍历的写法先不写了,比较复杂而且操作次数较多。
LeetCode 144 Binary Tree Preorder Traversal
方法1:递归
时间复杂度:O(n)
空间复杂度:O(h)最坏是O(n),不算结果数组,以下空间复杂度分析全都不算结果数组
想法:最基本的递归写法,res.add()在调用left和right之前。
代码:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
preOrder(root, res);
return res;
}
private void preOrder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
res.add(root.val);
preOrder(root.left, res);
preOrder(root.right, res);
}
}
方法2:迭代、栈
时间复杂度:O(n)
空间复杂度:O(h)最坏是O(n)
想法:用栈模拟递归,因为递归写法先调左子树,再调右子树,但是栈是LIFO,因此入栈的时候应该先入node.right,再入node.left.
代码:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root != null) {
stack.push(root);
}
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return res;
}
}
方法3:Morris traversal
时间复杂度:O(n)
空间复杂度:O(1)
想法:这Morris遍历可以举几个例子模拟一下。所以主要对我这种刷题面试的还是记住Morris遍历的原则,并会写代码。一下内容参考的帖子zhuanlan.zhihu.com/p/101321696 和www.acwing.com/blog/conten… .
Morris遍历的原则: 假设当前节点是root
- 如果
root.left == null
,则root = root.right
,直接continue - 如果
root.left != null
,要一路找到root左子树上最右的节点,也就是它的前驱,记为mostRight- 如果mostRight的right指针指向null,让其指向root,然后
cur=cur.left
- 如果mostRight的right指针指向root,则让其指向null,然后
cur=cur.right
第2点的第1点叫搭桥,第2点的第2点叫拆桥。preOrder traversal把元素放进结果数组当中,需要在搭桥这一步做掉。而对于inOrder traversal,则需要在拆桥这一步做掉。
- 如果mostRight的right指针指向null,让其指向root,然后
代码:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
while (root != null) {
if (root.left == null) {
res.add(root.val);
root = root.right;
}
else {
TreeNode mostRight = root.left;
while (mostRight.right != null && mostRight.right != root) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = root;
res.add(root.val);
root = root.left;
}
else {
mostRight.right = null;
root = root.right;
}
}
}
return res;
}
}
LeetCode 94 Binary Tree Inorder Traversal
方法1:递归
时间复杂度:O(n)
空间复杂度:O(h)最坏是O(n)
想法:最基本的递归写法,res.add()在调用left和right之间。
代码:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
inOrder(root, res);
return res;
}
private void inOrder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
inOrder(root.left, res);
res.add(root.val);
inOrder(root.right, res);
}
}
方法2:迭代、栈
时间复杂度:O(n)
空间复杂度:O(h)最坏是O(n)
想法:跟那道Binary Tree Iterator一模一样,用栈实现中序遍历。这个做法其实也就像很多做法一样...确实有它的道理,但到最后面试的时候就成了默写模板了。但是基本上来说,这个写法的意思是,一开始一路向左走,把这一路的TreeNode全压进栈。每次pop出来一个元素的时候,pop出来的必须是按照中序遍历的顺序,因此每次pop出来一个节点,就需要把它的下一个节点加进栈。它的下一个节点,实际上就是,加入它的右子树不为空的话,先去右孩子节点,然后一路向左,把这一路的TreeNode全压进栈,这样的话栈顶就是刚才这个节点的后继。
代码:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
List<Integer> res = new ArrayList<>();
while (root != null) {
stack.push(root);
root = root.left;
}
while (!stack.isEmpty()) {
TreeNode head = stack.pop();
res.add(head.val);
if (head.right != null) {
head = head.right;
while (head != null) {
stack.push(head);
head = head.left;
}
}
}
return res;
}
}
方法3:Morris traversal
时间复杂度:O(n)
空间复杂度:O(1)
想法:把上面的preOrder稍微做修改,res.add()放在拆桥这一步即可。
代码:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
while (root != null) {
if (root.left == null) {
res.add(root.val);
root = root.right;
}
else {
TreeNode mostRight = root.left;
while (mostRight.right != null && mostRight.right != root) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = root;
root = root.left;
}
else {
mostRight.right = null;
res.add(root.val);
root = root.right;
}
}
}
return res;
}
}
LeetCode 145 Binary Tree Postorder Traversal
方法1:递归
时间复杂度:O(n)
空间复杂度:O(h)最坏是O(n)
想法:最基本的递归写法,res.add()在调用left和right之后。
代码:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postOrder(root, res);
return res;
}
private void postOrder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
postOrder(root.left, res);
postOrder(root.right, res);
res.add(root.val);
}
}
方法2:迭代、栈
时间复杂度:O(n)
空间复杂度:O(h)最坏是O(n)
想法:我这道题是参考的花花酱zxi.mytechroad.com/blog/tree/l… 。意思是说,我直接把上面递归的代码改成用栈可能不是很好改,但是换个角度思考,假如要求的是后序遍历这个结果的reverse,会怎么样呢?我们就可以改成
private void postOrder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
res.add(root.val);
postOrder(root.right, res);
postOrder(root.left, res);
}
可以,那这样应该怎么写?发现这就跟前序遍历的递归非常像,只不过前序遍历里面是先去left再去right,我这里是先去right再去left,那就把前序遍历的栈的解法改改就好了。改出来stack while的时候就会变成
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
if (node.left != null) {
stack.push(node.left);
}
if (node.right != null) {
stack.push(node.right);
}
}
这样的话后放进来right,right就会被先pop出去,就实现了上面递归的伪代码。
最后,把结果数组反转,就做完了。
代码:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
if (node.left != null) {
stack.push(node.left);
}
if (node.right != null) {
stack.push(node.right);
}
}
Collections.reverse(res);
return res;
}
}