LeetCode 144, 94, 145

244 阅读3分钟

今天写二叉树的三种遍历,分别用递归,迭代和Morris遍历。按理来说是9种,但是后序遍历用Morris遍历的写法先不写了,比较复杂而且操作次数较多。

LeetCode 144 Binary Tree Preorder Traversal

链接:leetcode.com/problems/bi…

方法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/101321696www.acwing.com/blog/conten… .

Morris遍历的原则: 假设当前节点是root

  1. 如果root.left == null,则root = root.right,直接continue
  2. 如果root.left != null,要一路找到root左子树上最右的节点,也就是它的前驱,记为mostRight
    1. 如果mostRight的right指针指向null,让其指向root,然后cur=cur.left
    2. 如果mostRight的right指针指向root,则让其指向null,然后cur=cur.right 第2点的第1点叫搭桥,第2点的第2点叫拆桥。preOrder traversal把元素放进结果数组当中,需要在搭桥这一步做掉。而对于inOrder traversal,则需要在拆桥这一步做掉。

代码:

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

链接:leetcode.com/problems/bi…

方法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

链接:leetcode.com/problems/bi…

方法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;
    }
}