数据结构——求二叉树的属性

news2024/11/27 4:35:09

数据结构——求二叉树的属性

  • 一、对称性
    • 101. 对称二叉树
      • 1.递归
      • 2.迭代
      • 3.同类题:
  • 二、深度
    • 104. 二叉树的最大深度
      • 1.递归
        • 1)后序
        • 1)前序
      • 2.迭代(层序)
    • 559. N 叉树的最大深度
      • 1.递归(深度优先)
      • 2.迭代(广度优先)
    • 111. 二叉树的最小深度
      • 1.递归
      • 2.迭代
  • 三、高度
    • 110. 平衡二叉树
      • 1.递归
      • 2.迭代
  • 四、结点
    • 222. 完全二叉树的节点个数
      • 1.对普通二叉树的求法
      • 2.对完全二叉树的求法
  • 五、路径
    • 543. 二叉树的直径
      • 思路
    • 257. 二叉树的所有路径
      • 1.递归
      • 2.迭代
    • 112. 路径总和
      • 1.递归
      • 2.迭代
    • 113. 路径总和 II
      • 递归
  • 六、求值
    • 404. 左叶子之和
      • 1.递归
      • 2.迭代
    • 513. 找树左下角的值
      • 1.递归
    • 2.迭代

一、对称性

101. 对称二叉树

101. 对称二叉树

对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,其实我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。要比较的是两个子树的里侧和外侧的元素是否相等

本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。

因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中

1.递归

  • 递归函数参数为左子树结点和右子树结点,返回值为bool类型

  • 节点为空的情况有:

    • 左节点为空,右节点不为空,不对称,return false
    • 左不为空,右为空,不对称 return false
    • 左右都为空,对称,返回true
  • 此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:
    左右都不为空,比较节点数值,不相同就return false

  • 单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。

    • 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
    • 比较内测是否对称,传入左节点的右孩子,右节点的左孩子。
    • 如果左右都对称就返回true ,有一侧不对称就返回false 。
class Solution {
    boolean compare(TreeNode left,TreeNode right) {
        if (left==null&&right==null) return true;
        else if (left!=null&&right==null) return false;
        else if (left==null&&right!=null) return false;
        else if (left.val!=right.val) return false;
        // 左右节点都不为空,且数值相同的情况
        // 此时才做递归,做下一层的判断
        boolean outside = compare(left.left,right.right);//外侧比较
        boolean inside = compare(left.right,right.left);//内侧比较
        return outside && inside;
    }
    public boolean isSymmetric(TreeNode root) {
        if (root==null) return true;
        return compare(root.left,root.right);
    }
}

2.迭代

1)使用队列:

这里我们可以使用队列来比较两个树(根节点的左右子树)是否相互翻转,(注意这不是层序遍历)
迭代法,其实是把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。只要直接把队列改成栈就可以了。

代码随想录动画:
在这里插入图片描述

class Solution {
    public boolean isSymmetric(TreeNode root) {
        Queue<TreeNode> deque = new LinkedList<>();
        deque.offer(root.left);
        deque.offer(root.right);
        while (!deque.isEmpty()){
            TreeNode leftNode = deque.poll();
            TreeNode rightNode = deque.poll();
            if (leftNode == null && rightNode == null) {
                continue; //左右结点都为空,对称
            }
            if (leftNode == null && rightNode != null) return false;
            if (leftNode != null && rightNode == null) return false;
            if (leftNode.val != rightNode.val) return false;

            deque.offer(leftNode.left);  // 加入左节点左孩子
            deque.offer(rightNode.right);// 加入右节点右孩子(与左节点左孩子对应比较)
            deque.offer(leftNode.right); // 加入左节点右孩子
            deque.offer(rightNode.left); // 加入右节点左孩子(与左节点右孩子对应比较)
        }
        return true;
    }
}

2)使用双端队列:

也可以使用双端队列,相当于两个栈。这时结点孩子的入队顺序与上面不同

class Solution {
    public boolean isSymmetric(TreeNode root) {
        Deque<TreeNode> deque = new LinkedList<>();
        deque.offerFirst(root.left);
        deque.offerLast(root.right);
        while (!deque.isEmpty()){
            TreeNode leftNode = deque.pollFirst();
            TreeNode rightNode = deque.pollLast();
            if (leftNode == null && rightNode == null) {
                continue; //左右结点都为空,对称
            }
            if (leftNode == null && rightNode != null) return false;
            if (leftNode != null && rightNode == null) return false;
            if (leftNode.val != rightNode.val) return false;

            deque.offerFirst(leftNode.left);  // 先左节点左孩子
            deque.offerFirst(leftNode.right); // 后左节点右孩子
            deque.offerLast(rightNode.right); // 先右节点右孩子
            deque.offerLast(rightNode.left);   // 后右节点左孩子
        }
        return true;
    }
}

3.同类题:

100. 相同的树

给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

题解:
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        return compare(p, q);
    }
    public boolean compare(TreeNode tree1, TreeNode tree2) {
      if(tree1==null && tree2==null)return true;
      if(tree1==null || tree2==null)return false;
      if(tree1.val!=tree2.val)return false;
      // 此时是左右节点都不为空,且数值相同的情况
      // 此时才做递归,做下一层的判断
      boolean compareLeft = compare(tree1.left, tree2.left);       // 左子树:左、 右子树:左
      boolean compareRight = compare(tree1.right, tree2.right);    // 左子树:右、 右子树:右
      boolean isSame = compareLeft && compareRight;                  // 左子树:中、 右子树:中
      return isSame;
  }
}

572. 另一棵树的子树

给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

题解:
要判断一个树subRoot是不是树root的子树,那么可以判断subRoot是否和树root的任意子树相等。即,在root的每个子节点上,判断该子节点是否和subRoot相等
subRoot是root的左子树subRoot是root的右子树subRoot与root相同就说明root中包含和subRoot具有相同结构和节点值的子树。

class Solution {
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if (subRoot==null) return true;
        if (root==null) return false;
        boolean left = isSubtree(root.left,subRoot);  //subRoot是否是root的左子树
        boolean right = isSubtree(root.right,subRoot);//subRoot是否是root的右子树
        boolean equal = compare(root,subRoot);        //subRoot是否与root相同
        return left||right||equal;  //或关系
    }
    //判断是同一颗树
    public boolean compare(TreeNode tree1, TreeNode tree2) {
      if(tree1==null && tree2==null)return true;
      if(tree1==null || tree2==null)return false;
      if(tree1.val!=tree2.val)return false;              
      return compare(tree1.left, tree2.left)&&compare(tree1.right, tree2.right);
  }
}

PS:本题还可用串匹配或树哈希优化。

二、深度

104. 二叉树的最大深度

104. 二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例: 给定二叉树 [3,9,20,null,null,15,7],
在这里插入图片描述
返回它的最大深度 3 。

1.递归

本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。而根节点的高度就是二叉树的最大深度

  • 递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型。
  • 终止条件:如果为空节点的话,就返回0,表示高度为0。
  • 单层递归的逻辑:先求它的左子树的深度,再求的右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。

1)后序

class Solution {
    public int maxDepth(TreeNode root) {
        if (root==null) return 0;
        int leftdepth = maxDepth(root.left);       // 左
        int rightdepth = maxDepth(root.right);     // 右
        int depth = 1 + Math.max(leftdepth, rightdepth); // 中
        return depth;
    }
}

1)前序

使用了前序(中左右)的遍历顺序,这才是真正求深度的逻辑

class Solution {
    int result;
    void getdepth(TreeNode node, int depth) {
        result = depth > result ? depth : result; // 中

        if (node.left == null && node.right == null) return ;

        if (node.left!=null) { // 左
            depth++;    // 深度+1
            getdepth(node.left, depth);
            depth--;    // 回溯,深度-1
        }
        if (node.right!=null) { // 右
            depth++;    // 深度+1
            getdepth(node.right, depth);
            depth--;    // 回溯,深度-1
        }
//	   上面表现出了深度回溯过程,可以简化为以下:
//        if (node.left) { 
//            getdepth(node/left, depth + 1);
//        }
//        if (node.right) { 
//            getdepth(node.right, depth + 1);
//        }
        return ;
    }
    public int maxDepth(TreeNode root) {
        result = 0;
        if (root==null) return 0;
        getdepth(root, 1);
        return result;
    }
}

2.迭代(层序)

迭代法使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。

在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度

class Solution {
    public int maxDepth(TreeNode root) {
        if (root==null) return 0;
        Deque<TreeNode> deque = new LinkedList<>();
        deque.offer(root);
        int depth = 0;
        while (!deque.isEmpty()) {
            int size = deque.size();
            depth++;
            for (int i = 0; i < size; i++) {
                TreeNode poll = deque.poll();
                if (poll.left != null) {
                    deque.offer(poll.left);
                }
                if (poll.right != null) {
                    deque.offer(poll.right);
                }
            }
        }
        return depth;
    }
}

559. N 叉树的最大深度

559. N 叉树的最大深度

给定一个 N 叉树,找到其最大深度。

最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。

N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔

在这里插入图片描述
输入:root = [1,null,3,2,4,null,5,6]
输出:3

1.递归(深度优先)

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( h i g h t ) O(hight) O(hight)

class Solution {
    public int maxDepth(Node root) {
        if (root==null) return 0;
        int depth = 0;
        if (root.children!=null) {
            for (Node child : root.children) {
                depth = Math.max(depth,maxDepth(child));
            }
        }
        return depth+1;
    }
}

2.迭代(广度优先)

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

class Solution {
    public int maxDepth(Node root) {
        if (root==null) return 0;
        int depth = 0;
        Queue<Node> que = new LinkedList<>();
        que.offer(root);
        while (!que.isEmpty())
        {
            depth++;
            int len = que.size();
            while (len > 0)
            {
                Node node = que.poll();
                for (int i = 0; i < node.children.size(); i++)
                    if (node.children.get(i) != null) 
                        que.offer(node.children.get(i));
                len--;
            }
        }
        return depth;
    }
}

111. 二叉树的最小深度

111. 二叉树的最小深度

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。

示例:

给定二叉树 [3,9,20,null,null,15,7],
在这里插入图片描述
返回它的最小深度 2.

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

1.递归

使用后序遍历,因为要比较递归返回之后的结果

  • 递归函数的参数和返回值:参数为要传入的二叉树根节点,返回的是int类型的深度。
  • 终止条件:也是遇到空节点返回0,表示当前节点的高度为0。
  • 单层递归的逻辑:如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。
    反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。
    最后,如果左右子树都不为空,返回左右子树深度最小值 + 1 。
class Solution {
    public int minDepth(TreeNode root) {
        if (root==null) return 0;
        int leftDepth = minDepth(root.left);   //左
        int rightDepth = minDepth(root.right); //右
                                               //中
        // 当一个左子树为空,右不为空,这时并不是最低点
        if (root.left==null) return rightDepth+1;
        // 当一个右子树为空,左不为空,这时并不是最低点
        if (root.right==null) return leftDepth+1;
        // 左右结点都不为空
        return Math.min(leftDepth,rightDepth)+1;
    }
}

2.迭代

使用层序遍历

只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点

class Solution {
    public int minDepth(TreeNode root) {
        if (root==null) return 0;
        int depth = 0;
        Deque<TreeNode> deque = new LinkedList<>();
        deque.offer(root);
        while (!deque.isEmpty()) {
            int size = deque.size();
            depth++; // 记录最小深度
            for (int i = 0; i < size; i++) {
                TreeNode node = deque.poll();
                if (node.left == null && node.right == null) {
                    // 是叶子结点,直接返回depth,从上往下遍历,所以该值就是最小值
                    return depth;
                }
                if (node.left != null) deque.offer(node.left);
                if (node.right != null) deque.offer(node.right);
            }
        }
        return depth;
    }
}

三、高度

110. 平衡二叉树

110. 平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

示例 1:

给定二叉树 [3,9,20,null,null,15,7]
在这里插入图片描述
返回 true 。

PS:关于根节点的深度究竟是1 还是 0,不同的地方有不一样的标准,leetcode的题目中都是以节点为一度,即根节点深度是1。但维基百科上定义用边为一度,即根节点的深度是0

因为求深度可以从上到下去查,所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)

1.递归

  • 参数和返回值:参数:当前传入节点。返回值:以当前传入节点为根节点的树的高度。
  • 终止条件:遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0
  • 单层递归的逻辑:分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则则返回-1,表示已经不是二叉平衡树了。

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

class Solution {
    public boolean isBalanced(TreeNode root) {
        return getHeight(root) != -1;
    }
    int getHeight(TreeNode root) {
        if (root == null) return 0;
        int leftHeight = getHeight(root.left);
        if (leftHeight == -1) return -1;
        int rightHeight = getHeight(root.right);
        if (rightHeight == -1) return -1;
        // 左右子树高度差大于1,return -1表示已经不是平衡树了
        if (Math.abs(leftHeight - rightHeight) > 1) {
            return -1;
        }
        return Math.max(leftHeight, rightHeight) + 1;
    }
}

2.迭代

可以先定义一个函数,专门用来求高度。这个函数通过栈模拟的后序遍历找每一个节点的高度(其实是通过求传入节点为根节点的最大深度来求的高度)然后再用栈来模拟前序遍历,遍历每一个节点的时候,再去判断左右孩子的高度是否符合
此题迭代效率较低,计算高度时会重复遍历
时间复杂度: O ( n 2 ) O(n^2) O(n2)

四、结点

222. 完全二叉树的节点个数

222. 完全二叉树的节点个数

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。

示例 1:

输入:root = [1,2,3,4,5,6]
输出:6
示例 2:

输入:root = []
输出:0
示例 3:

输入:root = [1]
输出:1
提示:

树中节点的数目范围是[0, 5 * 10^4]
0 <= Node.val <= 5 * 10^4
题目数据保证输入的树是 完全二叉树

1.对普通二叉树的求法

1)递归

先求它的左子树的节点数量,再求的右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( log ⁡ n ) O(\log n) O(logn)

class Solution {
    public int countNodes(TreeNode root) {
        if(root == null) return 0;
        return countNodes(root.left) + countNodes(root.right) + 1;
    }
}

2)迭代

加一个变量result,统计节点数量

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

class Solution {
    public int countNodes(TreeNode root) {
        if (root == null) return 0;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int result = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            while (size -- > 0) {
                TreeNode cur = queue.poll();
                result++; // 记录节点数量
                if (cur.left != null) queue.offer(cur.left);
                if (cur.right != null) queue.offer(cur.right);
            }
        }
        return result;
    }
}

2.对完全二叉树的求法

完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。

对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。

对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。

时间复杂度: O ( log ⁡ n × log ⁡ n ) O(\log n × \log n) O(logn×logn)
空间复杂度: O ( log ⁡ n ) O(\log n) O(logn)

class Solution {
    public int countNodes(TreeNode root) {
        if (root == null) return 0;
        int leftDepth = getDepth(root.left);// 求左子树深度
        int rightDepth = getDepth(root.right);// 求右子树深度
        if (leftDepth == rightDepth) {// 左子树是满二叉树
            // 2^leftDepth其实是 (2^leftDepth - 1) + 1 ,左子树 + 根结点
            return (1 << leftDepth) + countNodes(root.right);
        } else {// 右子树是满二叉树
            return (1 << rightDepth) + countNodes(root.left);
        }
    }
    int getDepth(TreeNode root) {
        int depth = 0;
        while (root != null) {
            root = root.left;
            depth++;
        }
        return depth;
    }
}

五、路径

543. 二叉树的直径

543. 二叉树的直径
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

示例 :
给定二叉树

      1
     / \
    2   3
   / \     
  4   5    

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

思路

每一条二叉树的「直径」长度,就是一个节点的左右子树的最大深度之和。
求整棵树中的最长「直径」,那直接思路就是遍历整棵树中的每个节点,然后通过每个节点的左右子树的最大深度算出每个节点的「直径」,最后把所有「直径」求个最大值即可。

应该把计算「直径」的逻辑放在后序位置,后序位置可得到左右子树的最大深度。

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( h i g h t ) O(hight) O(hight)

class Solution {
    int maxDiameter = 0;
    public int diameterOfBinaryTree(TreeNode root) {
        maxDepth(root);
        return maxDiameter;
    }
    //求最大深度
    int maxDepth(TreeNode root) {
    if (root == null) return 0;
    int leftMax = maxDepth(root.left);
    int rightMax = maxDepth(root.right);
    //后序位置计算直径
    int myDiameter = leftMax + rightMax;
    maxDiameter = Math.max(maxDiameter, myDiameter);

    return 1 + Math.max(leftMax, rightMax);
    }
}

257. 二叉树的所有路径

257. 二叉树的所有路径

给定一个二叉树,返回所有从根节点到叶子节点的路径。

说明: 叶子节点是指没有子节点的节点。

示例:
在这里插入图片描述

1.递归

要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。要把路径记录下来,需要回溯来回退一个路径进入另一个路径。
在这里插入图片描述

  • 函数函数参数以及返回值:要传入根节点,记录每一条路径的path,和存放结果集的result,这里递归不需要返回值。用List记录路径,方便下面的回溯
  • 递归终止条件:当 cur不为空,其左右孩子都为空的时候,就找到叶子节点,开始结束的处理逻辑。
  • 单层递归逻辑:因为是前序遍历,需要先处理中间节点,中间节点就是我们要记录路径上的节点,先放进path中。然后是递归和回溯的过程,上面没有判断cur是否为空,那么在这里递归的时候,如果为空就不进行下一层递归。
class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList<>();
        if (root == null) return res;
        List<Integer> paths = new ArrayList<>();
        traversal(root, paths, res);
        return res;
    }
    void traversal(TreeNode root, List<Integer> paths, List<String> res) {
        paths.add(root.val);
        // 叶子结点
        if (root.left == null && root.right == null) {
            // 输出
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < paths.size() - 1; i++) {
                sb.append(paths.get(i)).append("->");
            }
            sb.append(paths.get(paths.size() - 1));
            res.add(sb.toString());
            return;
        }
        if (root.left != null) {
            traversal(root.left, paths, res);
            paths.remove(paths.size() - 1);// 回溯
        }
        if (root.right != null) {
            traversal(root.right, paths, res);
            paths.remove(paths.size() - 1);// 回溯
        }
    }
}

2.迭代

使用前序遍历的迭代方式来模拟遍历路径的过程,可以直接定义一个成员变量为object的栈,同时模拟递归和存放遍历路径。

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> result = new ArrayList<>();
        if (root == null)
            return result;
        Stack<Object> stack = new Stack<>();
        // 节点和路径同时入栈
        stack.push(root);
        stack.push(root.val + "");
        while (!stack.isEmpty()) {
            // 节点和路径同时出栈
            String path = (String) stack.pop();
            TreeNode node = (TreeNode) stack.pop();
            // 若找到叶子节点
            if (node.left == null && node.right == null) {
                result.add(path);
            }
            //右子节点不为空
            if (node.right != null) {
                stack.push(node.right);
                stack.push(path + "->" + node.right.val);
            }
            //左子节点不为空
            if (node.left != null) {
                stack.push(node.left);
                stack.push(path + "->" + node.left.val);
            }
        }
        return result;
    }
}

112. 路径总和

112. 路径总和

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

说明: 叶子节点是指没有子节点的节点。

示例: 给定如下二叉树,以及目标和 sum = 22,
在这里插入图片描述
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。

1.递归

可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树

  • 递归函数的参数和返回类型:参数需要二叉树的根节点,还需要一个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。要找一条符合条件的路径,所以递归函数需要返回值,及时返回。返回值可用布尔类型表示,路径总和等于目标值就返回true。
  • 终止条件:累加然后判断是否等于目标和,代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
    如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
    如果遍历到了叶子节点,count不为0,就是没找到。
  • 单层递归的逻辑:终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
    递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if (root==null) return false;
        targetSum -= root.val;
        //叶子节点
        if (root.left==null&&root.right==null)
            return targetSum==0;//计数是为0,则存在路径
        if (root.left != null) {
            boolean left = hasPathSum(root.left, targetSum);
            if (left) return true; // 已经找到
        }
        if (root.right != null) {
            boolean right = hasPathSum(root.right,targetSum);
            if (right) return true;
        }
        return false;
    }
}

递归简化:

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if (root == null) return false; // 为空退出
        // 叶子节点判断是否符合
        if (root.left == null && root.right == null) return root.val == targetSum;
        // 求两侧分支的路径和
        return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
    }
}

递归函数什么时候需要返回值?什么时候不需要返回值?

如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(113.路径总和ii)
如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (236. 二叉树的最近公共祖先)
如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题112. 路径总和)

2.迭代

栈里一个元素不仅要记录该节点指针,还要记录从头结点到该节点的路径数值总和。可以使用两个栈分别记录。相对复杂一些。

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if(root==null) return false;
        Stack<TreeNode> stack1 = new Stack<>();
        Stack<Integer> stack2 = new Stack<>();
        stack1.push(root); stack2.push(root.val);
        while(!stack1.isEmpty()){
            int size = stack1.size();
            for(int i=0;i<size;i++){
                TreeNode node = stack1.pop(); int sum=stack2.pop();
                // 如果该节点是叶子节点了,同时该节点的路径数值等于sum,那么就返回true
                if(node.left==null && node.right==null && sum==targetSum) return true;
                // 右节点,压进去一个节点的时候,将该节点的路径数值也记录下来
                if(node.right!=null){
                    stack1.push(node.right); stack2.push(sum+node.right.val);
                }
                // 左节点,压进去一个节点的时候,将该节点的路径数值也记录下来
                if(node.left!=null){
                    stack1.push(node.left); stack2.push(sum+node.left.val);
                }
            }
        }
        return false;
    }
}

113. 路径总和 II

113. 路径总和 II

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

说明: 叶子节点是指没有子节点的节点。

示例: 给定如下二叉树,以及目标和 sum = 22,
在这里插入图片描述

递归

要遍历整个树,找到所有路径,所以递归函数不要返回值

法1:

class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) return res; // 非空判断
        
        List<Integer> path = new LinkedList<>();
        preorderdfs(root, targetSum, res, path);
        return res;
    }
    void preorderdfs(TreeNode root,int targetSum,List<List<Integer>> res,List<Integer> path) {
        path.add(root.val);
        // 遇到了叶子节点
        if (root.left == null && root.right == null) {
            // 找到了和为 targetSum 的路径
            if (targetSum - root.val == 0) {
                res.add(new ArrayList<>(path));
            }
            return; // 如果和不为 targetSum,返回
        }

        if (root.left != null) {
            preorderdfs(root.left, targetSum - root.val, res, path);
            path.remove(path.size() - 1); // 回溯
        }
        if (root.right != null) {
            preorderdfs(root.right, targetSum - root.val, res, path);
            path.remove(path.size() - 1); // 回溯
        }
    }
}

法2:

class Solution {
    List<List<Integer>> result;
    LinkedList<Integer> path;
    public List<List<Integer>> pathSum (TreeNode root,int targetSum) {
        result = new LinkedList<>();
        path = new LinkedList<>();
        travesal(root, targetSum);
        return result;
    }
    void travesal(TreeNode root,  int count) {
        if (root == null) return;
        path.offer(root.val);
        count -= root.val;
        if (root.left == null && root.right == null && count == 0) {
            result.add(new LinkedList<>(path));
        }
        travesal(root.left, count);
        travesal(root.right, count);
        path.removeLast(); // 回溯
    }
}

六、求值

404. 左叶子之和

404. 左叶子之和

计算给定二叉树的所有左叶子之和。

示例:
在这里插入图片描述
注意:
如果一个节点的左节点不为空,且左节点没有左右孩子,那么这个节点的左节点就是左叶子。判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。

1.递归

递归的遍历顺序为后序遍历(左右中),是因为要通过递归函数的返回值来累加求取左叶子数值之和。

  • 递归函数的参数和返回值:要传入树的根节点,递归函数的返回值为数值之和,所以为int
  • 终止条件:root == NULL
  • 单层递归的逻辑:判断当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        if (root == null) return 0;
        int leftValue = sumOfLeftLeaves(root.left);    // 左
        int rightValue = sumOfLeftLeaves(root.right);  // 右
                                                       
        int midValue = 0;
        if (root.left != null && root.left.left == null && root.left.right == null) { 
            midValue = root.left.val;
        }
        int sum = midValue + leftValue + rightValue;  // 中
        return sum;
    }
}

2.迭代

前中后序和层序遍历都可

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
迭代前序:

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        if (root == null) return 0;
        Stack<TreeNode> stack = new Stack<> ();
        stack.add(root);
        int result = 0;
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            if (node.left != null && node.left.left == null && node.left.right == null) {
                result += node.left.val;
            }
            if (node.right != null) stack.add(node.right);
            if (node.left != null) stack.add(node.left);
        }
        return result;
    }
}

迭代层序:

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        int sum = 0;
        if (root == null) return 0;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            while (size -- > 0) {
                TreeNode node = queue.poll();
                if (node.left != null) { // 左节点不为空
                    queue.offer(node.left);
                    if (node.left.left == null && node.left.right == null){ // 左叶子节点
                        sum += node.left.val;
                    }
                }
                if (node.right != null) queue.offer(node.right);
            }
        }
        return sum;
    }
}

513. 找树左下角的值

513. 找树左下角的值

给定一个二叉树,在树的最后一行找到最左边的值

示例 1:
在这里插入图片描述

1.递归

深度最大的叶子节点一定是最后一行,所以要找深度最大的叶子节点。找最左边的可以使用前序遍历,这样优先在左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。

  • 参数和返回值:参数有要遍历树的根节点,还有一个int型的变量用来记录最大深度。 不需要返回值。(如果需要遍历整棵树,递归函数就不能有返回值。如果需要遍历某一条固定路线,递归函数就一定要有返回值!
    本题还需要类里的两个全局变量,maxLen用来记录最大深度,maxleftValue记录最大深度最左节点的数值。
  • 终止条件:遇到叶子节点就更新最大深度
  • 单层递归逻辑:求最大深度,递归的过程中依然要使用回溯
class Solution {
    int maxLen = 0;
    int maxleftValue;
    public int findBottomLeftValue(TreeNode root) {
        maxleftValue = root.val;
        findLeftValue(root,0);
        return maxleftValue;
    }

    void findLeftValue(TreeNode root,int deep) {
        if (root==null) return;
        if (root.left==null && root.right==null) {
            if (deep>maxLen) {
                maxleftValue = root.val;
                maxLen = deep;
            }
        }
        //隐藏回溯
        if (root.left != null) findLeftValue(root.left,deep + 1);
        if (root.right != null) findLeftValue(root.right,deep + 1);
    }
}

2.迭代

本题更适合使用层序遍历,只需要记录最后一行第一个节点的数值就可以了。

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int res = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode poll = queue.poll();
                if (i == 0) //每层最左端
                    res = poll.val;
                if (poll.left != null) 
                    queue.offer(poll.left);
                if (poll.right != null) 
                    queue.offer(poll.right);
            }
        }
        return res;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/460534.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB实现OCR识别数字和字符

OCR也叫做光学字符识别&#xff0c;是计算机视觉研究领域的分支之一。它是利用光学技术和计算机技术把印在或写在纸上的文字读取出来&#xff0c;并转换成一种计算机能够接受、人又可以理解的格式。 MATLAB实现OCR识别数字和字符&#xff0c;涉及灰度转换、中值滤波、二值化处…

【教学类-34-05】拼图(彩色图片+凹凸拼)3*4格子(中班主题《个别化拼图》偏美术)

图片展示&#xff1a; 背景需求&#xff1a; 最近班级孩子得了传染病&#xff0c;来了2位孩子。 我觉得&#xff1a;人少的话&#xff0c;孩子们就有充足的时间去拼那些带有凹凸槽的自制彩色图形拼图。 难点&#xff1a; 1、从直线剪切&#xff08;方形拼图&#xff09;转…

[JAVA编] 一编让你搞定多态

目录 1. 多态概念 2. 多态的体现和实现条件 3. 重写 4. 引用类型转换 4.1向上转型 4.2向下转型 5. 多态的好处 1.多态的概念 什么是多态? 多态是继封装, 继承之后, 面向对象的三大特性 在生活中,比如跑的动作,猫,狗和大象,跑起来都不一样.再比如飞…

【cmd命令】MySQL服务器无法启动

winR 输入services.msc 我发现我电脑上有两个mysql名&#xff0c;上面一个&#xff08;MySQL&#xff09;处于停止运行状态 下面一个&#xff08;MySQL)处于运行状态 如果要使用上面一个的服务器&#xff0c;就要把下面一个的服务器关闭&#xff0c;然后启动上面的服务器…

elastic-job 搭建——应用于企业级项目

1. &#x1f4c2; 技术方案 方案介绍 ElasticJob 是面向互联网生态和海量任务的分布式调度解决方案。 它通过弹性调度、资源管控、以及作业治理的功能&#xff0c;打造一个适用于互联网场景的分布式调度解决方案&#xff0c;并通过开放的架构设计&#xff0c;提供多元化的作业…

你知道渲染农场是什么原理吗?它是如何工作的?

我们知道&#xff0c;仅靠一台计算机几乎是不能达到专业渲染集群的处理能力的。所以现在&#xff0c; 允许将很多台计算机或是处理器进行连接&#xff0c;再将连接后的机器作为一个总平台来处理不同的渲染需求&#xff0c;这样的设置&#xff0c;就被称之为渲染农场。 渲染农…

年轻人“赶烤”淄博,文旅业如何借势?

​&#xff08;图片来源于网络&#xff0c;侵删&#xff09; 文 | 螳螂观察 作者 | 易不二 从“更适合中国宝宝体质的TACO”在社交媒体爆火&#xff0c;到全国人民为之“赶烤”&#xff0c;淄博凭借独树一帜的烧烤文化&#xff0c;已经站上了文旅业回暖的潮头。 今年五一假期…

FPGA目前就业形势咋样?来听听业内工程师的看法

看到网上有一个问题很火&#xff1a;2023了&#xff0c;FPGA目前就业形势咋样?很多同学也对这个方向比较感兴趣&#xff0c;下面就来一起了解一下吧。 FPGA岗位有哪些&#xff1f; 从芯片设计流程来看&#xff0c;FPGA岗位可以分四类 产品开发期&#xff1a;FPGA系统架构师 …

基于FFmpeg倒放功能的实现-----命令行和API调用实现方法

来源:微信公众号「编程学习基地」 文章目录 FFmpeg API调用reverse滤镜实现视频倒放ffmpeg命令行实现方法FFmpeg 过滤器 调用API实现方法完整代码贴上运行FFmpeg API调用reverse滤镜实现视频倒放 ffmpeg命令行实现方法 ffmpeg -i bigbuckbunny_480x272.h265 -filter_comple…

vue页面内嵌iframe使用postMessage进行数据交互(postMessage跨域通信)

什么是postMessage postMessage是html5引入的API,它允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档,多窗口,跨域消息传递.多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案. vue父页面&#xff08;嵌入iframe的页面&#xff09; 在vue中…

【文心一言】文心一言最近这么火,它到底是什么

前言 文心一言&#xff08;英文名&#xff1a;ERNIE Bot&#xff09;是百度全新一代知识增强大语言模型&#xff0c;文心大模型家族的新成员&#xff0c;能够与人对话互动&#xff0c;回答问题&#xff0c;协助创作&#xff0c;高效便捷地帮助人们获取信息、知识和灵感。文心一…

网工容易混淆的三种网线类型:直连线、交叉线和反转线

网线是计算机网络中最常见的传输介质之一&#xff0c;它能够将数据快速而可靠地传输到各个网络设备中。在实际的网络应用中&#xff0c;我们常常需要使用到不同类型的网线&#xff0c;包括直连线、交叉线和反转线。本文将介绍这三种网线的定义、应用场景和注意事项。 直连线 …

Tailwind CSS入门(二)——基本介绍和特性

上一篇文章简要的介绍了原子类CSS&#xff0c;以及个人对语义化、原子化的一些经验和理解。从这篇文章开始&#xff0c;正式开始分享Tailwind CSS的特性、使用和技巧。 Tailwind CSS是一个为快速开发而精心设计的原子类CSS框架&#xff0c;在此我们将搭建一个Vite项目来配合讲…

力扣---LeetCode21. 合并两个有序链表(链表经典题)

文章目录 前言21. 合并两个有序链表链接&#xff1a;方法一&#xff1a;取小尾插1.1代码&#xff1a;1.2 流程图&#xff1a;1.3 注意&#xff1a; 方法二&#xff1a;带哨兵位2.1代码&#xff1a;2.2流程图&#xff1a; 总结 前言 焦虑不会消除明天的悲伤 只会让你今天的力量…

springboot概述

脚手架: 因为创建的为web项目&#xff0c;有这两个文件夹 在idea中也可以使用脚手架 会直接或间接包含依赖 启动类 单一模块: 启动类要放在根包下边&#xff0c;其他的业务放在根包或者根包的子包 多个模块: restController包含controller且每个方法都包含responseBody注解&…

这可能是你看过最详细的Java集合篇【二】—— LinkedList

文章目录 LinkedList继承关系数据结构变量构造方法添加元素相关方法查找元素相关方法删除元素相关方法清空方法遍历方法其它方法常见面试题 LinkedList LinkedList底层数据结构是双向链表。链表数据结构的特点是每个元素分配的空间不必连续、插入和删除元素时速度非常快、但访…

10年+工作经验总结:测试工程师职业成长路线图

一、功能测试工程师必备技能 1. 功能测试理论 主要包括&#xff1a; 软件测试流程 需求理解 测试用例设计&#xff08;编写测试用例的策略&#xff09; 执行测试用例 提交bug(bug是由什么组成&#xff0c;bug处理流程&#xff0c;bug优先级&#xff0c;bug的定位等) 回归…

基于Vue的web设计打印方案

企业信息化例如ERP,OA等等都会存在纸质单据打印的情况&#xff0c;需要在企业内部流转&#xff0c;打印设计也有很多方案&#xff0c;例如fastReport,bartender等等&#xff0c;今天要说的是 vue-plugin-hiprint&#xff0c;开源的web打印插件&#xff0c;基于此插件可以集成模…

SpringBoot 中如何正确的实现模块日志入库?

目录 1.简述2.踩坑记录3.LoginController4.LoginService5.LoginLogService5.1 Async实现异步5.2 自定义线程池实现异步1&#xff09;自定义线程池2&#xff09;复制上下文请求3&#xff09;自定义线程池实现异步 6.补充&#xff1a;LoginService 手动提交事务 背景&#xff1a;…

并发编程之可重入锁ReentrantLock

文章目录 前言ReentrantLock原理ReentrantLock VS Synchronized源码解析ReentrantLock同步机制ReentrantLock可重入机制ReentrantLock可中断机制ReentrantLock超时机制条件变量Condition 写在最后 前言 大家都知道在并发编程中一般会用到多线程技术&#xff0c;多线程技术可以…