【刷题笔记06】树

刷题笔记系列

【刷题笔记01】- 数组
【刷题笔记02】-链表
【刷题笔记03 -哈希表】
【刷题笔记04】- 字符串
【刷题笔记05】-栈与队列


1.树的遍历

1.1 二叉树前中后序遍历

1.1.1 前序遍历

https://leetcode.cn/problems/binary-tree-preorder-traversal/

    • 递归法的代码(其他顺序就是换下add处理节点的位置
class Solution {
    List<Integer> result = new ArrayList<>();
    public List<Integer> preorderTraversal(TreeNode root) {
        if(root != null){
            result.add(root.val);
            preorderTraversal(root.left);
            preorderTraversal(root.right);
        }
        return result;
    }
}
    • 迭代法的代码
class Solution {
   public List<Integer> preorderTraversal(TreeNode root) {
       List<Integer> result = new ArrayList<>();
       Stack<TreeNode> st = new Stack<>();
       st.push(root);
       while(!st.empty()){
           TreeNode cur = st.pop();
           if(cur != null){
               result.add(cur.val);
               st.push(cur.right);
               st.push(cur.left);
           }
       }
       return result;
   }
}

优化:push前判空,为空就不push了,减少入栈出栈的次数。

1.1.2 中序遍历

https://leetcode.cn/problems/binary-tree-inorder-traversal/

    • 递归法
class Solution {
    List<Integer> result = new ArrayList<>();
    public List<Integer> inorderTraversal(TreeNode root) {
        if(root != null){
            preorderTraversal(root.left);
            result.add(root.val);
            preorderTraversal(root.right);
        }
        return result;
    }
}
    • 迭代法
   public List<Integer> inorderTraversal(TreeNode root) {
       List<Integer> result = new ArrayList<>();
       Stack<TreeNode> st = new Stack<>();
       if(root != null) st.push(root);
       TreeNode cur = root;
       while(cur != null || !st.empty()){
           if(cur!= null){
               if(cur.left != null) st.push(cur.left);
               cur = cur.left;
           }else{
               cur = st.pop();
               result.add(cur.val);
               if(cur.right != null) st.push(cur.right);
               cur = cur.right;
           }
       }
       return result;
   }

中序push时加上了判空反而不合适,也没必要分两个分支都去push。这样逻辑上挺乱的。

    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> st = new Stack<>();
        TreeNode cur = root;
        while(cur != null || !st.empty()){
            if(cur!= null){
                st.push(cur);
                cur = cur.left;
            }else{
                cur = st.pop();
                result.add(cur.val);
                cur = cur.right;
            }
        }
        return result;
    }

这样优化后入栈和出栈存结果集就分到两个分支中处理了。
中序遍历:左中右
我们访问节点其实都是从父节点开始,通过父节点就能找到子节点。
cur指针的移动 结合 栈中的数据,就可以判断出当前节点的位置。
当cur不等于null时,往栈中push节点并cur = cur.left 通过left一直往左找,直到 cur = cur.left 后 cur为null,就会从栈中取元素,加入结果集,并将cur指向它的right。

1.1.3 后序遍历

https://leetcode.cn/problems/binary-tree-postorder-traversal/

    • 递归法
class Solution {
    List<Integer> result = new ArrayList<>();
    public List<Integer> postorderTraversal(TreeNode root) {
        if(root != null){
            preorderTraversal(root.left);
            preorderTraversal(root.right);
            result.add(root.val);
        }
        return result;
    }
}
    • 迭代法
   public List<Integer> postorderTraversal(TreeNode root) {
       List<Integer> result = new ArrayList<>();
       Stack<TreeNode> st = new Stack<>();
       TreeNode cur = root;
       TreeNode lastVist = root;

       while(cur != null || !st.isEmpty()){
           if(cur.left != null){
               st.push(cur);
               cur = cur.left;
           }else{
               TreeNode temp = st.peek();
               if(temp.right != null && temp.right != lastVist){
                   cur = temp.right;
               }else{
                   result.add(temp.val);
                   st.pop();
                   lastVist = temp;
               }
           }
       }
       return result;
   }

1.1.4 二叉树三种遍历的统一迭代法——标记法

    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> st = new Stack<>();
        TreeNode cur = root;
        if(cur != null) st.push(cur);
        while(!st.empty()){
            cur = st.pop();
            if(cur != null){
                //根据遍历顺序不同存入
                st.push(cur);
                st.push(null);
                if(cur.right != null) st.push(cur.right);
                if(cur.left != null) st.push (cur.left);
            }else{
                result.add(st.pop().val);
            }
        }
        return result;
    }

这个写法,push节点时就必须要判空了,因为这个写法的核心就是用null标记。

拓展:树的遍历可以把栈换成队列吗?
不可以。
树是一种递归的数据结构,用队列无法达到一条路径上的回溯。
举个例子,对于前序遍历二叉树[1,4,3,2]
若在处理了节点1后往队列中放入节点4 和 节点3
处理节点4时将其子节点2放入队列,而此时队列中节点3在节点2之前,按队列的存取规则,显然先取3再取2,显然不符合dfs的结果。

总结:
前序遍历因为访问和处理顺序一致,所以可以先把root push了,while循环只要判断栈/队列是否为空,取出节点就行;
但是中序和后序都是先要访问到最左子节点处,再处理节点;所以cur指向root,

1.1.5 N叉树的遍历

N叉树只有前序遍历和后序遍历,没有中序遍历的说法,因为无法确定哪几枝是左和右

  • N叉树的前序遍历
    https://leetcode.cn/problems/n-ary-tree-preorder-traversal/
    public List<Integer> preorder(Node root) {
        List<Integer> result = new ArrayList<>();
        Stack<Node> st = new Stack<>();
        if(root != null) st.push(root);
        while(!st.isEmpty()){
            Node cur = st.pop();
            result.add(cur.val);
            for(int i = cur.children.size()-1; i >= 0; i--){
                st.push(cur.children.get(i));
            }
        }
        return result;
    }
  • N叉树的后序遍历
    https://leetcode.cn/problems/n-ary-tree-postorder-traversal/
class Solution {
    public List<Integer> postorder(Node root) {
        List<Integer> result = new ArrayList<>();
        Stack<Node> st = new Stack<>();
        if(root != null) st.push(root);
        while(!st.isEmpty()){
            Node cur = st.pop();
            if(cur != null){
                st.push(cur);
                st.push(null);
                for(int i = cur.children.size()-1; i >= 0; i--){
                    st.push(cur.children.get(i));
                }
            }else{
                result.add(st.pop().val);
            }
            
        }
        return result;
    }
}

套路和二叉树的遍历类似的,掌握了二叉树前中后序统一迭代法,N叉也类似。

2. LeetCode236 二叉树的公共祖先

https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/

  • 思路:
    首先确定遍历顺序:后序遍历 (因为需要根据子节点的返回情况判断当前节点是否满足条件;所以要先遍历再处理节点;若用先序或中序,则无法收集结果)

  • 代码

    • ×错误解法×
      在这里插入图片描述
      这里又让我联想起快慢指针移除数组重复元素。有时候条件正向不好处理的,不如用排除法做,下面正解就是巧妙运用了这个思路。
    • √ 正解
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q) 
            return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right =  lowestCommonAncestor(root.right, p, q);
        if(left!=null && right!=null)
            return root;
        if(left!=null && right==null)
            return left;
        if(right!=null && left==null)
            return right;
        return null;
    }
    • √ 优化
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || root == p || root == q){
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if(right == null)
            return left;
        if(left == null)
            return right;
        return root;
    }

3、LeetCode129 求根节点到叶子节点组成的数字的和

https://leetcode.cn/problems/sum-root-to-leaf-numbers/description/

  • 代码

    • × 错误解法 ×
      在这里插入图片描述
    • √ 正解
class Solution {
    public int sumNumbers(TreeNode root) {
        return dfs(root, 0);
    }

    public int dfs(TreeNode root, int prevSum) {
        if(root == null){
            return 0;
        }
        // 每往下一层就要乘10,先做这个处理比较方便
        int sum = prevSum*10 + root.val;
        if(root.left == null && root.right == null){
            return sum;
        }else{
        return  dfs(root.left, sum) + dfs(root.right, sum);
        }
       
    }
}

总结

  1. 树的遍历、图的遍历有时候需要借助标记法标记处理过左右孩子、访问过相邻节点 的 节点
  2. 在所有可能的情况很明确的情况下,用逆向思考、排除法或许是个不错的选择。
  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值