数据结构与算法刷题之【树】篇

文章目录

前言

除了去年11月份以及今年近几月的算法刷题之外,只有在当时20年蓝桥杯准备的时候才刷过一些题,在当时就有接触到一些动归、递归回溯、贪心等等,不过那会也还是一知半解,做的题目也特别少,因为考虑到之后面试有算法题以及数据结构算法对于一个程序员十分重要,我也开始了刷题之路。

我目前的学习数据结构与算法及刷题路径:

1、学习数据结构的原理以及一些常见算法。

2、代码随想录:跟着这个github算法刷题项目进行分类刷,在刷题前可以学习一下对应类别的知识点,而且这里面每道题都讲的很详细。

3、牛客网高频面试101题:牛客网—面试必刷101题,在刷的过程中可以在leetcode同步刷一下。

4、接下来就是力扣上的专栏《剑指offer II》、《程序员面试金典(第 6 版)》...有对应的精选题单来对着刷即可。

5、大部分的高频面试、算法题刷完后,就可以指定力扣分类专栏进行一下刷题了。

刚开始刷的时候真的是很痛苦的,想到去年一道题可能就需要好几小时,真的就很难受的,不过熬过来一切都会好起来,随着题量的增多,很多题目你看到就会知道使用什么数据结构或者算法来去求解,并且思考对应的时间空间复杂度,并寻求最优解,我们一起加油!

我的刷题历程

截止2022.8.18:

1、牛客网101题(其中1题是平台案例有问题):image-20220818095030215

2、剑指offerII:image-20220818095104757

力扣总记录数:image-20220818095148897

加油加油!

二叉树基础知识点

1、二叉树的种类

满二叉树:只有度为0的结点和度为2的结点。

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

image-20211107165848019

二叉搜索树:

若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树

image-20211107165921579

平衡二叉搜索树:AVL(Adelson-Velsky and Landis)树,是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

  • 红黑树就是一种二叉平衡搜索树。

image-20211107170014672

2、二叉树的存储方式

一般利于表示我们使用链式存储

顺序存储:数组存储。

  • 父节点的数组下表是i,那么它的左孩子就是i * 2 + 1,右孩子就是 i * 2 + 2

image-20211107170147507

链式存储:链表形式存储。

image-20211107170202397

3、二叉树的遍历方式

深度优先遍历:递归

  • 前序遍历(递归法,迭代法)
  • 中序遍历(递归法,迭代法)
  • 后序遍历(递归法,迭代法)

广度优先遍历

  • 层次遍历(迭代法)

4、二叉树节点定义

class TreeNode{
    private int val;
    private TreeNode left;
    private TreeNode right;
    
    public TreeNode(){
    }

    public TreeNode(int val){
        this.val = val;
    }

    public TreeNode(int val,TreeNode left, TreeNode right){
        this(val);
        this.left = left;
        this.right = right;
    }
}

剑指offer

剑指 Offer 54. 二叉搜索树的第k大节点【简单】

题目链接:剑指 Offer 54. 二叉搜索树的第k大节点

题目内容:给定一棵二叉搜索树,请找出其中第 k 大的节点的值。

思路:二叉搜索树(左根右查询出来的是排序的),找第k大的节点值。

1、右根左。然后使用一个变量来进行计数。

复杂度:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
class Solution {
     
    public int kthLargest(TreeNode root, int k) {
        dfs(root, k);
        return val;
    }
    
    private int n;
    private int val;
    
    public void dfs(TreeNode root, int k) {
        if (root == null) {
            return;
        }
        dfs(root.right, k);
        n++;
        if (n == k) {
            val = root.val;
            return;
        }
        dfs(root.left, k);
    }
}

剑指 Offer 36. 二叉搜索树与双向链表【中等】

本题可在本章节牛客网-13题来进阶练习

题目链接:剑指 Offer 36. 二叉搜索树与双向链表

题目内容:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

思路:中序遍历(递归)+两个指针。

  • ps:与本章节中牛客网13有不同的就是首位指针需要进行连接。

image-20220712104543435

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
/*
// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;

    public Node() {}
    
    public Node(int _val) {
        val = _val;
    }
    
    public Node(int _val,Node _left,Node _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
    private Node head;
    private Node pre;

    public Node treeToDoublyList(Node root) {
        dfs(root);
        //dfs之后就是将尾部与首部连接起来
        if (head != null) {
            head.left = pre;
            pre.right = head;
        }
        return head;
    }
    
    public void dfs(Node root) {
        if (root == null) {
            return;
        }
        treeToDoublyList(root.left);
        //第一次情况or后面n次情况
        if (head == null) {
            head = root;
            pre = root;
        }else {
            pre.right = root;
            root.left = pre;
            pre = root;
        } 
        treeToDoublyList(root.right);
    }
}

剑指 Offer 32 - II. 从上到下打印二叉树 II【中等】

题目链接:剑指 Offer 32 - II. 从上到下打印二叉树 II

题目内容:从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

思路:

1、迭代遍历使用队列【通过】

复杂度分析:

  • 时间复杂度:O(n),实际上2n
  • 空间复杂度:O(n):一个是队列n、一个是list,实际上是3n。
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        //队列
        Queue<TreeNode> queue = new LinkedList<>();
        if (root != null) {
            queue.offer(root);
        }
        while (!queue.isEmpty()) {
            List<Integer> nums = new ArrayList<>();
            List<TreeNode> list = new ArrayList<>();
            while (!queue.isEmpty()) {
                list.add(queue.poll());
            }
            for (TreeNode node: list) {
                nums.add(node.val);
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            res.add(nums);
        }
        return res;
    }
}

2、递归,层数

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {

    private List<List<Integer>> res = new ArrayList<List<Integer>>();

    public List<List<Integer>> levelOrder(TreeNode root) {
        dfs(root, 1);
        return res;
    }

    //k表示层数,node表示当前该层的结点
    public void dfs(TreeNode node, int k) {
        if (node != null) {
            if (res.size() < k) res.add(new ArrayList<Integer>());
            res.get(k - 1).add(node.val);
            dfs(node.left, k + 1);
            dfs(node.right, k + 1);
        }
    }

}

剑指 Offer 26. 树的子结构【中等】

题目链接:剑指 Offer 26. 树的子结构

题目内容:输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)。B是A的子结构, 即 A中有出现和B相同的结构和节点值。

思路:

1、dfs,遍历每个结点的同时,来对AB结点进行递归调用判定。

复杂度分析:时间复杂度O(MN),空间复杂度O(M),M,N谁的调用栈深,就是哪个。

class Solution {

    //该函数遍历每一个结点
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        if (A == null || B == null) {
            return false;
        }
        //第一种写法:
        // if (A.val == B.val && helper(A.left, B.left) && helper(A.right, B.right)) {
        //     return true;
        // }
        //第二种写法:
        if (helper(A, B)) {
            return true;
        }
        return isSubStructure(A.left, B) || isSubStructure(A.right, B);
    }
    
    //判断各自从当前结点开始是否一致,也就是说是否同是树的子结构
    public boolean helper(TreeNode A, TreeNode B) {
        //若是B当前为null,表示此时已经比较完毕
        if (B == null) {
            return true;
        }
        if (A == null) {
            return false;
        }
        //此时A、B都!=null
        if (A.val != B.val) {
            return false;
        }
        //只有左右相等才行
        return helper(A.left, B.left) && helper(A.right, B.right);
    }
}

剑指 Offer 33. 二叉搜索树的后序遍历序列【中等】

题目链接:剑指 Offer 33. 二叉搜索树的后序遍历序列

题目内容:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

思路:

1、构建二叉搜索树

复杂度分析:时间复杂度O(n);空间复杂度O(n)

class Solution {

    private int end;
    
    //思路:以最后一个节点作为中间点,然后借助end坐标不断的去尝试在某个区间范围中看是否能够真的插入,例如[rootVal, max], [min, rootVal]
    public boolean verifyPostorder(int[] postorder) {
        this.end = postorder.length - 1;
        build(postorder, Integer.MIN_VALUE, Integer.MAX_VALUE);
        return end < 0;
    }
    
    public void build(int[] postorder, int min, int max) {
        if (end < 0) { return; }
        //取得最后一个节点
        int rootVal = postorder[end];
        if (rootVal >= max || rootVal <= min) { return; }
        //只有符合条件,end才会-1表示该点是符合要求的
        end--;
        //接着尝试去挂载下一个节点
        build(postorder, rootVal, max);
        build(postorder, min, rootVal);
    }
}

剑指offer37. 序列化二叉树【困难,等同于牛客网的17】

题目链接: 序列化二叉树

牛客网

二叉树的前序、中序、后续遍历【简单】

题目:二叉树的前序遍历

题目描述:给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param root TreeNode类 
     * @return int整型一维数组
     */
    public int[] preorderTraversal (TreeNode root) {
        List<Integer> list = new ArrayList();
        preorder(list, root);
        //返回的结果
        int[] res = new int[list.size()];
        for (int i = 0; i < list.size(); i++) 
            res[i] = list.get(i);
        return res;
    }
    
    //使用List集合来进行收集
    public void preorder(List<Integer> list, TreeNode root) {
        if (root == null) 
            return;
        list.add(root.val);
        preorder(list, root.left);
        preorder(list, root.right);
    }

}

对于中序,后序遍历只需要在preorder中改变下list.add的位置即可。

中序遍历(骚操作)

思路:利用栈,首先遍历添加所有左节点到栈中,接着出栈,记录值,接着结点变为右节点。

/**
 * Definition for a binary tree node.
 * public 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;
 *     }
 * }
 */
class Solution {
    
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Stack<TreeNode> s = new Stack<>();
        TreeNode head = root;
        while (head != null || !s.isEmpty()) {
            while (head != null) {
                s.push(head);
                head = head.left;
            }
            TreeNode node = s.pop();
            res.add(node.val);
            head = node.right;
        }
        return res;
    }
}

二叉树的最大深度【简单】

题目链接:二叉树的最大深度

题目描述:求给定二叉树的最大深度,深度是指树的根节点到任一叶子节点路径上节点的数量。最大深度是所有叶子节点的深度的最大值。

思路1:通过递归来求出最大深度。

复杂度分析:

  • 时间复杂度:O(n):遍历整个节点数。
  • 空间复杂度:O(n):最坏情况下,二叉树化为链表,递归栈深度最大为n。

​```java import java.util.*;

/*

  • public class TreeNode {
  • int val = 0;
  • TreeNode left = null;
  • TreeNode right = null;
  • } */

public class Solution { /** * * @param root TreeNode类 * @return int整型 */ public int maxDepth (TreeNode root) { //最底层为0 if (root == null) { return 0; } //取左右结点的最大层数 return Math.max(maxDepth(root.left) + 1, maxDepth(root.right) + 1); } }


## 二叉树中和为某一值的路径(一)【简单】

题目链接:[二叉树中和为某一值的路径(一)](https://www.nowcoder.com/practice/508378c0823c423baa723ce448cbfd0c?tpId=295&tqId=634&ru=/exam/oj&qru=/ta/format-top101/question-ranking&sourceUrl=%2Fexam%2Foj)

题目内容:给定一个二叉树root和一个值 sum ,判断是否有从根节点到叶子节点的节点值之和等于 sum 的路径。

**思路1**:采用递归来进行计算值。

复杂度分析:

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

```java
import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 * }
 */

public class Solution {
    
    private boolean flag = false;
    
    /**
     * 
     * @param root TreeNode类 
     * @param sum int整型 
     * @return bool布尔型
     */
    public boolean hasPathSum (TreeNode root, int sum) {
        if (root == null) {
            return false;
        }
        //若是当前结点为叶子结点
        if (root.left == null && root.right == null && sum - root.val == 0) {
            return true;
        }
        //递归左右结点
        return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
    }
    
}

升级版:leetcode剑指 Offer 34. 二叉树中和为某一值的路径【中等】

题目地址:剑指 Offer 34. 二叉树中和为某一值的路径

题目内容:给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

  • 叶子节点 是指没有子节点的节点。

自己思路(不太行):采用递归+回溯的思路来进行解决【自己想的】

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
/**
 * Definition for a binary tree node.
 * public 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;
 *     }
 * }
 */
class Solution {

    private List<List<Integer>> res = new ArrayList<>();

    public List<List<Integer>> pathSum(TreeNode root, int target) {
        recursion(root, new ArrayList<Integer>(), target);
        return res;
    }

    public void recursion(TreeNode root, List<Integer> list, int target) {
        if (root == null) {
            return;
        }
        //若是叶子结点以及路径值为目标值
        if (root.left == null && root.right == null) {
            int index = list.size();
            list.add(index, root.val);
            if (isTargetNum(list, target)) {
                res.add(buildNewList(list));
            }
            list.remove(index);
            return;
        }
        int index = list.size();
        list.add(index, root.val);
        //递归
        recursion(root.left, list , target);
        //回溯
        list.remove(index);
        list.add(index, root.val);
        recursion(root.right, list, target);
        list.remove(index);
    }

    public boolean isTargetNum(List<Integer> list, int target) {
        if (list == null) {
            return false;
        }
        int sum = 0;
        for (int num : list) {
            sum += num;
        }
        return sum == target;
    }

    public List<Integer> buildNewList(List<Integer> list) {
        List<Integer> destList = new ArrayList<>();
        destList.addAll(list);
        return destList;
    }

}

思路1:dfs,通过递归来进行求解。

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
/**
 * Definition for a binary tree node.
 * public 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;
 *     }
 * }
 */
class Solution {
    List<List<Integer>> ret = new LinkedList<List<Integer>>();
    Deque<Integer> path = new LinkedList<Integer>();

    public List<List<Integer>> pathSum(TreeNode root, int target) {
        dfs(root, target);
        return ret;
    }

    public void dfs(TreeNode root, int target) {
        if (root == null) {
            return;
        }
        //填充该结点
        path.addLast(root.val);
        target -= root.val;
        //叶子结点满足的路径情况
        if (root.left == null && root.right == null && target == 0) {
            ret.add(new LinkedList<Integer>(path));
        }
        //递归调用
        dfs(root.left, target);
        dfs(root.right, target);
        //回溯
        path.removeLast();
    }

}

image-20220628215339305

对称的二叉树【简单】

学习:leetcode题解、 代码随想录—101. 对称二叉树

题目地址:对称的二叉树

题目内容:给定一棵二叉树,判断其是否是自身的镜像(即:是否对称)

思路1:根左右 + 根右左来进行比对,思路依旧是使用递归方式来进行

import java.util.*;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public boolean recursion(TreeNode root1, TreeNode root2) {
        //最终符合情况
        if (root1 == null && root2 == null) {
            return true;
        }
        //中间比较差错情况
        if (root1 == null || root2 == null || root1.val != root2.val) {
            return false;
        }
        //左右子树对称比较
        return recursion(root1.left, root2.right) && recursion(root1.right, root2.left);
    }
    
    boolean isSymmetrical(TreeNode pRoot) {
        return recursion(pRoot, pRoot);
    }
}

2、迭代法

思路:其实与递归的思路大致相同,同样也是比较的左右节点(同外侧或内侧),只不过这里会使用队列来进行临时存储要比对的两个左右节点,每次出队同时出队两个,入队时要入4个(每个节点都有左右孩子)。

//迭代法:使用队列
public boolean isSymmetric(TreeNode root) {
    Deque<TreeNode> queue = new LinkedList<>();
    if(root == null){
        return true;
    }
    queue.offer(root.left);
    queue.offer(root.right);
    while(!queue.isEmpty()){
        //每次出队2个元素
        TreeNode nodeLeft = queue.poll();
        TreeNode nodeRight = queue.poll();
        if(nodeLeft == null && nodeRight != null){
            return false;
        }else if(nodeLeft != null && nodeRight == null){
            return false;
        }else if(nodeLeft == null && nodeRight == null){
            continue;
        }else if(nodeLeft.val != nodeRight.val){
            return false;
        }

        //依次入队
        queue.offer(nodeLeft.left);
        queue.offer(nodeRight.right);
        queue.offer(nodeLeft.right);
        queue.offer(nodeRight.left);
    }
    return true;
}

image-20211108224529852

上面的是先入队,之后来进行统一判断的,后来我又写了一个在入队时进行判断的,写完后想了想实际上效果也不大,因为最多仅仅只是提前来对两对节点进行判断而已,这里的话就贴一下:

//迭代法:使用队列
public boolean isSymmetric(TreeNode root) {
    Deque<TreeNode> queue = new LinkedList<>();
    if(root == null || isNull(root.left,root.right)){
        return true;
    }
    if(!addQueue(root.left,root.right,queue)){
        return false;
    }

    while(!queue.isEmpty()){
        //每次出队2个元素
        TreeNode nodeLeft = queue.poll();
        TreeNode nodeRight = queue.poll();

        if(!isNull(nodeLeft.left,nodeRight.right)){
            if(!addQueue(nodeLeft.left,nodeRight.right,queue)){
                return false;
            }
        }
        if(!isNull(nodeLeft.right,nodeRight.left)){
            if(!addQueue(nodeLeft.right,nodeRight.left,queue)){
                return false;
            }
        }

    }
    return true;
}

public boolean addQueue(TreeNode node1,TreeNode node2,Deque queue){
    if(isSymmetry(node1,node2)){
        queue.offer(node1);
        queue.offer(node2);
        return true;
    }else{
        return false;
    }
}

public boolean isNull(TreeNode leftNode,TreeNode rightNode){
    return leftNode == null && rightNode == null;
}

/**
     * 判断是否对称
     * @param left
     * @param right
     * @return true表示允许将两个节点入队,false则表示不对称
     */
public boolean isSymmetry(TreeNode left,TreeNode right){
    if(left == null && right != null){
        return false;
    }else if(left != null && right == null) {
        return false;
    }else if(left.val == right.val){
        return true;
    }
    //        }else if(left == null && right == null){  //该判断要进行抽离
    //            return true;
    //        }
    return false;
}

image-20211108224431155

合并二叉树【简单】

题目链接:合并二叉树

题目内容:已知两颗二叉树,将它们合并成一颗二叉树。合并规则是:都存在的结点,就将结点值加起来,否则空的位置就由另一个树的结点来代替。

思路:采用递归构建左右子树的做法来进行合并二叉树,是通过进行前序遍历。

复杂度分析:

  • 时间复杂度:O(min(n, m)),m和n分别为两棵树的结点树,当一个树访问完时,自然就连接到了另一个数的节点
  • 空间复杂度:O(min(n, m)),递归栈的深度与访问时间也相同,只访问了小树的结点树。
public class Solution {
    /**
     * 
     * @param t1 TreeNode类 
     * @param t2 TreeNode类 
     * @return TreeNode类
     */
    public TreeNode mergeTrees (TreeNode t1, TreeNode t2) {
        if (t1 == null) {
            return t2;
        }
        if (t2 == null) {
            return t1;
        } 
        TreeNode node = new TreeNode(t1.val + t2.val);
        node.left = mergeTrees(t1.left, t2.left);
        node.right = mergeTrees(t1.right, t2.right);
        return node;
    }
    
}

判断是不是平衡二叉树【简单】

类似题:剑指 Offer 55 - II. 平衡二叉树

题目链接:判断是不是平衡二叉树

题目内容:输入一棵节点数为 n 二叉树,判断该二叉树是否是平衡二叉树。在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树

  • 平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

思路1:自顶向下

  • 每棵子树的左右子树都要去判断

复杂度分析:

  • 时间复杂度:O(n2)
  • 空间复杂度:O(n)
public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        //平衡二叉树
        if (root == null) {
            return true;
        }
        int left = deep(root.left);
        int right = deep(root.right);
        if (left - right > 1 || left - right < -1) {
            return false;
        } 
        return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
    }
    
    //求得该结点的最大结点深度
    public int deep(TreeNode node) {
        if (node == null) {
            return 0;
        }
        int left = deep(node.left);
        int right = deep(node.right);
        return left > right ? left + 1 : right + 1;
    }
    
}

思路2:自底向上(最优解)

  • 直接从底部来进行比较高度来进行分析,然后不断的将结果向上传送。

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        if (root == null) {
            return true;
        }
        return getDeep(root) != -1;
    }
    
    //求得该结点的最大结点深度
    public int getDeep(TreeNode root) {
        if (root == null) {
            return 0;
        }
        
        int left = getDeep(root.left);
        if (left < 0) {
            return -1;
        }
        int right = getDeep(root.right);
        if (right < 0) {
            return -1;
        }
        return Math.abs(left - right) > 1 ? -1 : 1 + Math.max(left, right);
    }
    
}

二叉搜索树的最近公共祖先【简单】

题目地址:二叉搜索树的最近公共祖先

题目内容:给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

  • 二叉搜索树规律:二叉搜索树,左节点比父节点小,右节点比父节点大。

思路1:获取目标1、目标2的路径元素,然后比对遍历路径中的元素,最后相同的元素即为最近公共祖先。

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 *   public TreeNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param root TreeNode类 
     * @param p int整型 
     * @param q int整型 
     * @return int整型
     */
    public int lowestCommonAncestor (TreeNode root, int p, int q) {
        //获取到两个结点的路径
        ArrayList<Integer> path1 = getPath(root, p);
        ArrayList<Integer> path2 = getPath(root, q);
        int res = -1;
        //比对路径元素
        for (int i = 0; i < path1.size() && i < path2.size(); i++) {
            int val1 = path1.get(i);
            int val2 = path2.get(i);
            if (val1 == val2) {
                res = val1;
            }else {
                break;
            }
        }
        return res;
    }
    
    //获取二叉搜索树指定结点的一个路径元素
    public ArrayList<Integer> getPath(TreeNode root, int target) {
        ArrayList<Integer> path = new ArrayList<Integer>();
        TreeNode cur = root;
        while (cur != null && cur.val != target) {
            path.add(cur.val);
            //当前结点>目标值
            if (cur.val > target) {
                cur = cur.left;
            }else{
                cur = cur.right;
            }
        }
        path.add(cur.val);
        return path;
    }
    
}

二叉树的镜像【简单】

题目地址:二叉树的镜像

题目内容:操作给定的二叉树,将其变换为源二叉树的镜像。

思路1:借助后序遍历,来进行左右节点交换。

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 *   public TreeNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pRoot TreeNode类 
     * @return TreeNode类
     */
    public TreeNode Mirror (TreeNode pRoot) {
        if (pRoot == null) {
            return null;
        }
        //后序遍历
        TreeNode left = Mirror(pRoot.left);
        TreeNode right = Mirror(pRoot.right);
        pRoot.left = right;
        pRoot.right = left;
        return pRoot;
    }
}

重建二叉树【中等】

题目: 重建二叉树

题目描述:给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。

思路1:利用递归,不断的找出对应的第一个结点,然后左右递归得到子树。

import java.util.*;
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    
    //思路:利用递归,不断的找出对应的第一个结点,然后左右递归得到子树
    public TreeNode reConstructBinaryTree(int [] pre,int [] vin){
        if (pre.length == 0) {
            return null;
        }
        int rootVal = pre[0];
        if (pre.length == 1) {
            return new TreeNode(rootVal);
        }
        //确定点
        TreeNode root = new TreeNode(rootVal);
        int rootIndex = 0;
        //找到中间点
        for (int i = 0; i < vin.length; i++) {
            if (vin[i] == rootVal) {
                rootIndex = i;
                break;
            }
        }
        //确定好中间点之后就是左右子树递归获取
        root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, rootIndex + 1), Arrays.copyOfRange(vin, 0, rootIndex));
        root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, rootIndex + 1, pre.length), Arrays.copyOfRange(vin, rootIndex + 1, vin.length));
        return root;
    }
}

求二叉树的层序遍历【中等】

题目链接:求二叉树的层序遍历

题目内容:给定一个二叉树,返回该二叉树层序遍历的结果,(从左到右,一层一层地遍历)。

思路1:BFS。通过队列来实现宽搜。

复杂度分析:

  • 时间复杂度:O(n)。
  • 空间复杂度:O(n)。
import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 * }
 */

public class Solution {
    /**
     * 
     * @param root TreeNode类 
     * @return int整型ArrayList<ArrayList<>>
     */
    public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
        //结果值
        ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
        //队列
        Deque<TreeNode> queue = new LinkedList<>();
        if (root != null) {
             queue.offer(root);   
        }
        //采用BFS
        while (!queue.isEmpty()) {
            //获取当前队列的数量
            int size = queue.size();
            ArrayList<Integer> list = new ArrayList<Integer>();
            //不断的队列中取出元素,然后进行获取值并且将左右结点再次入队
            while (size > 0) {
                TreeNode node = queue.poll();
                list.add(node.val);
                //入队
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
                size--;
            }
            res.add(list);
        }
        return res;
    }
}

思路2:dfs,采用递归的思路,前序遍历,根据对应深度来判断当前需要添加元素的集合位置。

复杂度分析:

  • 时间复杂度:O(n)。
  • 空间复杂度:O(n)。
import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 * }
 */

public class Solution {
    
    private ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
    
    /**
     * 
     * @param root TreeNode类 
     * @return int整型ArrayList<ArrayList<>>
     */
    public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
        dfsLevelOrder(root, 0);
        return res;
    }
    
    //采用dfs来进行遍历
    public void dfsLevelOrder(TreeNode root, int deep){
        if (root == null) {
            return;
        }
        deep++;
        //创建集合
        if (res.size() < deep) {
            res.add(new ArrayList<>());
        }
        //根据对应的深度索引来进行添加元素
        res.get(deep - 1).add(root.val);
        //递归遍历
        dfsLevelOrder(root.left, deep);
        dfsLevelOrder(root.right, deep);
    }
        
}

判断是不是二叉搜索树【中等】

题目地址:判断是不是二叉搜索树

题目内容:给定一个二叉树根节点,请你判断这棵树是不是二叉搜索树。二叉搜索树满足每个节点的左子树上的所有节点均严格小于当前节点且右子树上的所有节点均严格大于当前节点。

思路1:非递归。进行中序遍历得到所有结点,然后依次比较即可!

复杂度分析:

  • 时间复杂度:O(n)。递归了所有节点 + 遍历比较了一遍。
  • 空间复杂度:O(n)。递归了n次,存储中序遍历的所有节点。
import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 *   public TreeNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    private List<Integer> list = new ArrayList<Integer>();
    
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param root TreeNode类 
     * @return bool布尔型
     */
    public boolean isValidBST (TreeNode root) {
        recursion(root);
        //遍历比较路径中是否是排序
        for (int i = 1;i < list.size(); i++) {
            if (list.get(i - 1) >= list.get(i)) {
                return false;
            }
        }
        return true;
    }
    
    //中序遍历
    public void recursion (TreeNode root) {
        if (root == null) {
            return;
        }
        recursion(root.left);
        list.add(root.val);
        recursion(root.right);
    }
    
    
}

按之字形顺序打印二叉树【中等】

题目链接:按之字形顺序打印二叉树

题目内容:给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替)

思路1:队列+list集合+行号判定。

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
import java.util.*;
import java.util.ArrayList;

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        //结果集
        ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
        //队列
        Deque<TreeNode> q = new LinkedList<>();
        boolean con = true;//true表示奇数列,false表示偶数列
        //解决:pRoot为空情况
        if (pRoot != null) {
            q.offer(pRoot);
        }
        while (!q.isEmpty()) {
            ArrayList<Integer> r = new ArrayList<>();
            ArrayList<TreeNode> temp = new ArrayList<>();
            while (!q.isEmpty()) {
                temp.add(q.poll());
            }
            //判断奇偶性
            if (con) {
                for (TreeNode node : temp) {
                    r.add(node.val);
                }
            }else {
                for (int i = temp.size() - 1;i >= 0;i--) {
                    r.add(temp.get(i).val);
                }
            }
            //遍历一遍依次按照顺序添加到队列中
            for (int i = 0;i < temp.size();i++) {
                TreeNode node = temp.get(i);
                if (node.left != null) {
                    q.offer(node.left);
                }
                if (node.right != null) {
                    q.offer(node.right);
                }
            }
            res.add(r);
            con = !con;
        }
        return res;
    }

}

二叉搜索树与双向链表【中等】

题目链接:二叉搜索树与双向链表

题目内容:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。

题目主干信息:

1、将二叉搜索树转换为递增序的双向链表。
2、不能添加心得结点,需要在原结点基础上添加链表链接。
3、返回链表中的第一个节点的指针。
4、二叉树结点的左右指针看作是双向链表的前后指针。

思路1:中序遍历(递归模式),使用两个指针head、pre来进行表示。

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
import java.util.*;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    
    private TreeNode head;
    private TreeNode pre;
    
    //递归中序遍历
    public TreeNode Convert(TreeNode pRootOfTree) {
        if (pRootOfTree == null) {
            return null;
        }
        //中序遍历写法
        Convert(pRootOfTree.left);
        //第一次情况or后面n次情况
        if (head == null) {
            head = pRootOfTree;
            pre = pRootOfTree;
        }else {
            pre.right = pRootOfTree;
            pRootOfTree.left = pre;
            pre = pRootOfTree;
        }
        Convert(pRootOfTree.right);
        return head;
    }
}

判断是不是完全二叉树【中等】

题目链接:判断是不是完全二叉树

题目内容:给定一个二叉树,确定他是否是一个完全二叉树。完全二叉树的定义:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树。(第 h 层可能包含 [1~2h] 个节点)

思路1:层次遍历(队列),然后进行校验。

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 *   public TreeNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param root TreeNode类 
     * @return bool布尔型
     */
    public boolean isCompleteTree (TreeNode root) {
        //若是空树,则为完全二叉树
        if (root == null) {
            return true;
        }
        //层次遍历+中间校验
        Queue<TreeNode> queue = new LinkedList<>();
        boolean isComplete = false;
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (node == null) {
                isComplete = true;
                continue;
            }
            //此时不为null的结点(若是此时isComplete为true,那么表示不是完全二叉树了)
            if (isComplete) {
                return false;
            }
            //入队列,无需校验left、right是否为空了,因为需要进行判断是否是完全二叉树
            queue.offer(node.left);
            queue.offer(node.right);
        }
        return true;
    }
}

在二叉树中找到两个节点的最近公共祖先【中等】

题目链接:在二叉树中找到两个节点的最近公共祖先

题目内容:给定一棵二叉树(保证非空)以及这棵树上的两个节点对应的val值 o1 和 o2,请找到 o1 和 o2 的最近公共祖先节点。

思路1(不推荐):将对应值的路径通过dfs来放置到两个path集合中,之后对两个path集合进行一一比对,最终找出最近公共祖先。

  • 问题:可能会导出栈溢出,在牛客网debug时。

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n),递归2遍,两个集合元素,实际上大约为4n。
import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 * }
 */

public class Solution {
    
    
    public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
        ArrayList<Integer> path1 = new ArrayList<>();
        ArrayList<Integer> path2 = new ArrayList<>();
        //利用dfs深搜+回溯方法来找到对应的路径
        dfs(root, path1, o1);
        this.flag = false;//重置下标识
        dfs(root, path2, o2);
        //开始来找祖先结点
        int res = -1;
        for (int i = 0;i < path1.size() && i < path2.size();i++) {
            if (path1.get(i) == path2.get(i)) {
                res = path1.get(i);//找到最后相同的元素,也就是最近的公共组祖先
            }else{
                break;
            }
        }
        return res;
    }
    
    private boolean flag = false;
    
    public void dfs(TreeNode root, ArrayList<Integer> path, int target) {
        if (flag || root == null) {
            return;
        }
        path.add(root.val);
        //前序遍历
        if (root.val == target) {
            flag = true;
            return;
        }
        //处理元素
        dfs(root.left, path, target);
         if (flag){
            return;
        }
        dfs(root.right, path, target);
        //重点:放置在左右递归过程中找到了元素情况
        if (flag){
            return;
        }
        //回溯
        path.remove(path.size() - 1);
    }
    
}

思路2(推荐):递归思路,自底至上来进行递归处理。

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n),仅为递归调用所用的空间。
import java.util.*;

/*
 * public class TreeNode {
 *   int val = 0;
 *   TreeNode left = null;
 *   TreeNode right = null;
 * }
 */

public class Solution {
    
    
    public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
        //处理边界及返回值
        if (root == null) {
            return -1;
        }
        if (root.val == o1 || root.val == o2) {
            return root.val;
        }
        //得到左右两个值
        int left = lowestCommonAncestor(root.left, o1, o2);
        int right = lowestCommonAncestor(root.right, o1, o2);
        //-1即表示没有找到
        if (left == -1) {
            return right;
        }
        if (right == -1) {
            return left;
        }
        //若是两者都不是-1,那么其即为最近公共祖先
        return root.val;
    }
    
}

输出二叉树的右视图【中等】

学习视频:字节跳转高频算法面试题—第26题:二叉树的右视图

题目链接:输出二叉树的右视图

题目内容:请根据二叉树的前序遍历,中序遍历恢复二叉树,并打印出二叉树的右视图

思路1:重建二叉树+层次遍历二叉树+取得每层的最后一个结点。

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 求二叉树的右视图
     * @param xianxu int整型一维数组 先序遍历
     * @param zhongxu int整型一维数组 中序遍历
     * @return int整型一维数组
     */
    public int[] solve (int[] xianxu, int[] zhongxu) {
        //1、重建二叉树
        TreeNode root = reConstructBinaryTree(xianxu, zhongxu);
        //2、层次遍历,每次取得队列中最后一个元素
        Deque<TreeNode> queue = new LinkedList<>();
        if (root != null) {
            queue.offer(root);
        }
        ArrayList<Integer> res = new ArrayList<>();
        while (!queue.isEmpty()) {
            ArrayList<TreeNode> list = new ArrayList<>();
            while (!queue.isEmpty()) {
                list.add(queue.poll());
            }
            res.add(list.get(list.size() - 1).val);
            for (int i = 0;i < list.size();i++) {
                TreeNode node = list.get(i);
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
        }
        int[] resArray = new int[res.size()];
        for (int i = 0;i < res.size();i++) {
            resArray[i] = res.get(i);
        }
        return resArray;
    }
    
    //重构二叉树
     public TreeNode reConstructBinaryTree(int [] pre,int [] vin){
        if (pre.length == 0) {
            return null;
        }
        int rootVal = pre[0];
        if (pre.length == 1) {
            return new TreeNode(rootVal);
        }
        //确定点
        TreeNode root = new TreeNode(rootVal);
        int rootIndex = 0;
        //找到中间点
        for (int i = 0; i < vin.length; i++) {
            if (vin[i] == rootVal) {
                rootIndex = i;
                break;
            }
        }
        //确定好中间点之后就是左右子树递归获取
        root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, rootIndex + 1), Arrays.copyOfRange(vin, 0, rootIndex));
        root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, rootIndex + 1, pre.length), Arrays.copyOfRange(vin, rootIndex + 1, vin.length));
        return root;
    }
    
    public class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) { val = x; }
    }
}

序列化二叉树【中等】

题目链接:序列化二叉树

题目内容:请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。

思路:层次遍历,分别都采用队列+虚拟结点来进行实现。中间使用```符号来进行连接。

复杂度分析:

  • 空间复杂度:O(n)
  • 时间复杂度:O(n)
import java.util.*;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    
    //核心思路:层次遍历,采用队列+虚拟结点来进行实现
    private int INF = 151;
    private TreeNode emptyNode = new TreeNode(INF);

    String Serialize(TreeNode root) {
        StringBuilder str = new StringBuilder();
        if (root == null) {
            str.append(INF);
            return str.toString();
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            //判断是否是虚拟结点
            if (node.val != INF) {
                //不是虚拟结点
                queue.offer(node.left != null ? node.left : emptyNode);
                queue.offer(node.right != null ? node.right : emptyNode);
            }
            if (node != root) {
                str.append("_");
            }
            str.append(node.val);
        }
        return str.toString();
    }

    TreeNode Deserialize(String str) {
        //根据_来进行拆分
        String[] strs = str.split("_");
        TreeNode root = null;
        int firstVal = Integer.valueOf(strs[0]);
        if (str == null || str.length() == 0 || firstVal == INF) {
            return root;
        }
        //定义一个新节点
        root = new TreeNode(firstVal);
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        //根据字符串数量来进行添加
        for (int i = 1; i < strs.length - 1; i += 2) {
            TreeNode node = queue.poll();
            int a = Integer.valueOf(strs[i]), b = Integer.valueOf(strs[i + 1]);
            //a要么是INF,或者就是结点
            if (a != INF) {
                node.left = new TreeNode(a);
                queue.offer(node.left);
            }
            if (b != INF) {
                node.right = new TreeNode(b);
                queue.offer(node.right);
            }
        }
        return root;
    }
}

leetcode

二叉树的前中后序遍历(迭代遍历)【简单】

144. 二叉树的前序遍历

思路:使用栈来进行前序遍历,每次出栈一个元素的操作都是一致的:先读取出栈的元素值,紧接着出栈节点的右节点先入栈,左节点后入栈。

  • ps:入栈的节点不为null,先入右节点,后入左节点好处就是每次出栈得到的必是左节点的元素!
public List<Integer> preorderTraversal(TreeNode root) {
    Deque<TreeNode> stack = new LinkedList<>();
    List<Integer> nums = new ArrayList<>();
    if(root != null){
        stack.push(root);//先将头节点入栈
    }
    while(!stack.isEmpty()){
        TreeNode node = stack.pop();
        //从栈中读取节点的值(此时就会先读取到左节点的值,之后则是右节点的值)
        nums.add(node.val);
        //首先将节点的右节点入栈
        if(node.right != null){
            stack.push(node.right);
        }
        //接着将节点的左节点入栈
        if(node.left != null){
            stack.push(node.left);
        }
    }
    return nums;
}

94. 二叉树的中序遍历

思路:同样是用栈+指针的形式,指针用来寻路,栈中存储的都是待取值的一个个节点。若是当前节点为null,说明已经某个节点的左节点已经走完,走完的相同操作都是出栈一个元素,取到值之后开始继续对右节点进行探索。

//栈中存储的是多个待取值的节点
public List<Integer> inorderTraversal(TreeNode root) {
    Deque<TreeNode> stack = new LinkedList<>();
    List<Integer> nums = new ArrayList<>();
    TreeNode cur = root;
    while(cur != null || !stack.isEmpty()){
        //若是节点不为空,则直接入栈
        if(cur != null){
            stack.push(cur);
            cur = cur.left;//继续对节点的左节点遍历
        }else{ //若是节点为空,表名左节点已经探索结束
            //从栈中取出一个元素
            cur = stack.pop();
            nums.add(cur.val);//存储值
            cur = cur.right;//开始对右节点进行相同操作
        }
    }
    return nums;
}

145. 二叉树的后序遍历

思路:与前序遍历方式相同,前序遍历是先出栈取值,接着入栈右节点、左节点;这里的话先取值,接着入栈左节点、右节点,最终将取到的值进行反转。(前序遍历方式:左-右-中,这里取值刚好反过来中-右-左,最终反转即可)

//后序遍历取法 左-右-中  入栈顺序:中-右-左  ,与前序迭代遍历很相似,出栈元素取值。最终我们反转一下取到的值就是后序遍历取值
public List<Integer> postorderTraversal(TreeNode root) {
    Deque<TreeNode> stack = new LinkedList<>();
    List<Integer> nums = new ArrayList<>();
    if(root != null){
        stack.push(root);
    }
    while(!stack.isEmpty()){
        TreeNode node = stack.pop();
        nums.add(node.val);
        if(node.left != null){
            stack.push(node.left);
        }
        if(node.right != null){
            stack.push(node.right);
        }
    }
    Collections.reverse(nums);
    return nums;
}

二叉树的统一迭代法【简单】

相较于之前迭代遍历,之前的前后类似,中序遍历则存储、取值方式不大相同。

这里统一迭代法,对于前、中、后序遍历,只需要简单几行修改,与递归差不多。

思路:同样使用栈,按照对应的遍历方式来将所有的节点存储,接着来进行依次出栈取值,这里比较细节的一点就是要出栈取值的节点后会跟随这一个null值用于进行标识。

144. 二叉树的前序遍历

//统一迭代:前序遍历
public List<Integer> preorderTraversal(TreeNode root) {
    Deque<TreeNode> stack = new LinkedList<>();//根据指定的遍历顺序来进行存放
    List<Integer> result = new ArrayList<>();
    if(root != null){
        stack.push(root);
    }
    while(!stack.isEmpty()){
        TreeNode node = stack.peek();
        if(node != null){
            stack.pop();
            //前序遍历:在栈中存储方式为右-左-中
            if(node.right != null){
                stack.push(node.right);
            }
            if(node.left != null){
                stack.push(node.left);
            }
            //null则为之后记录遍历值时的标识
            stack.push(node);
            stack.push(null);
        }else{
            stack.pop();//先将null标识弹出,之后直接来统计拿到值
            TreeNode valNode = stack.pop();
            result.add(valNode.val);
        }
    }
    return result;
}

94. 二叉树的中序遍历

//统一迭代:中序遍历
public List<Integer> postorderTraversal(TreeNode root) {
    Deque<TreeNode> stack = new LinkedList<>();//根据指定的遍历顺序来进行存放
    List<Integer> result = new ArrayList<>();
    if(root != null){
        stack.push(root);
    }
    while(!stack.isEmpty()){
        TreeNode node = stack.peek();
        if(node != null){
            stack.pop();
            //中序遍历:在栈中存储方式为右-中-左
            if(node.right != null){
                stack.push(node.right);
            }
            stack.push(node);
            stack.push(null);
            if(node.left != null){
                stack.push(node.left);
            }

        }else{
            stack.pop();//先将null标识弹出,之后直接来统计拿到值
            TreeNode valNode = stack.pop();
            result.add(valNode.val);
        }
    }
    return result;
}

145. 二叉树的后序遍历

//统一迭代:后序遍历
public List<Integer> postorderTraversal(TreeNode root) {
    Deque<TreeNode> stack = new LinkedList<>();//根据指定的遍历顺序来进行存放
    List<Integer> result = new ArrayList<>();
    if(root != null){
        stack.push(root);
    }
    while(!stack.isEmpty()){
        TreeNode node = stack.peek();
        if(node != null){
            stack.pop();
            stack.push(node);
            stack.push(null);
            //中序遍历:在栈中存储方式为右-中-左
            if(node.right != null){
                stack.push(node.right);
            }
            if(node.left != null){
                stack.push(node.left);
            }

        }else{
            stack.pop();//先将null标识弹出,之后直接来统计拿到值
            TreeNode valNode = stack.pop();
            result.add(valNode.val);
        }
    }
    return result;
}

226. 翻转二叉树【简单】

学习:leetcode题解、 代码随想录—226.翻转二叉树

题目链接:226. 翻转二叉树

题目内容:给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

思路:

1、前序遍历+反转左右孩子

思路:使用前序递归遍历二叉树,每遍历一个二叉树节点过程中进行交换该节点的左右孩子,即可最终实现二叉树反转。

代码:

public TreeNode invertTree(TreeNode root) {
    invertTreeNode(root);
    return root;
}

/**
     * 前序遍历(递归),每遍历到一个元素对其左右孩子进行交换
     * @param root 当前节点
     */
public void invertTreeNode(TreeNode root){
    if( root == null ){
        return;
    }else{
        //交换该节点的左右节点
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
        //递归遍历左右节点
        invertTree(root.left);
        invertTree(root.right);
    }
}

111. 二叉树的最小深度【简单】

学习:leetcode题解、 代码随想录—二叉树的最小深度

题目链接:111. 二叉树的最小深度

题目内容:

给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。

思路:

1、递归

思路: 抓住这里的细节,这里的二叉树最小深度指的是从根节点到最近叶子节点的最短路径上的节点数量。也就是说不是叶子节点的就要进行额外处理,这里我们只需要在递归方法中进行对应情况匹配即可。

public int minDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    // 获取到左右节点
    int leftDeeps = minDepth(root.left);
    int rightDeeps = minDepth(root.right);

    // 情况1:左孩子为空,右孩子不为空
    if (root.left == null && root.right != null) {
        return rightDeeps + 1;
    } else if (root.left != null && root.right == null) {// 情况2:左孩子不为空,右孩子为空
        return leftDeeps + 1;
    }

    // 左右孩子都为空(叶子节点,左右为0,0)、左右孩子都不为空(通过比对递归得到的左右孩子的深度取最小深度值)
    return 1 + Math.min(leftDeeps, rightDeeps);
}

image-20211109172434312

543. 二叉树的直径【简单】

学习:leetcode题解

题目链接:543. 二叉树的直径

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

速记:

①深搜,本质就是在求得二叉树最大深度的递归过程中取得最大左右节点深度之和罢了。

思路:

1、深搜

直径指的是某个节点的左右节点深度之和。

①1的直径长度为1+2=3,左右深度之和。

image-20211112170321873

②2的直径长度为1+1=2,依旧是左右两边的深度之和。

image-20211112170432051

也就是说我们最终要找到指定某个节点的最大直径长度!

思路:使用深搜,思路与统计二叉树的深度一致,只不过在本题中是要求得二叉树的最大直径,那么在递归过程中需要不断计算左右节点深度之和的最大值,也就是说整个递归都是找到最大值所服务的。

复杂度分析:时间复杂度O(n),空间复杂度(Ologn):使用递归dfs这里树的深度即为空间复杂度。

class Solution {
    
    private int max;

    public int diameterOfBinaryTree(TreeNode root) {
        getMaxDepth(root);
        return max;
    }

    private int getMaxDepth(TreeNode root){
        if (root == null) {
            return 0;
        }
        //左边最大深度
        int l = getMaxDepth(root.left);
        //右边最大的深度
        int r = getMaxDepth(root.right);
        //若是该节点的左右节点的深度之和>max,则更新最大的深度值
        if ((l + r) > max) {
            max = l + r;
        }
        return Math.max(l, r) + 1;
    }
}

image-20211112170542309

559. N 叉树的最大深度【简单】

学习:leetcode题解、 代码随想录—n叉树的最大深度

题目链接:559. N 叉树的最大深度

题目内容:

给定一个 N 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔(请参见示例)。

思路:

1、递归

思路:通过递归来求取最大的深度。

代码:

private int maxDepth;

public int maxDepth(Node root) {
    recursionDepth(root,0);
    return maxDepth;
}

public void recursionDepth(Node root,int depth){
    if(root == null){
        return;
    }
    depth++;
    if(maxDepth<depth){
        maxDepth = depth;
    }
    for (Node child : root.children) {
        recursionDepth(child,depth);
    }
}

精简下代码:

public int maxDepth(Node root) {
    if (root == null) {
        return 0;
    }
    int depth = 0;
    for (Node child : root.children) {
        depth = Math.max(depth, maxDepth(child));//比较得到该层下方的最大深度
    }
    return depth + 1;
}

image-20211108232746974

2、迭代(队列)

思路:迭代遍历方式,过程中计算一下深度。

代码:

//迭代(使用队列)
public int maxDepth(Node root) {
    if (root == null) {
        return 0;
    }
    Deque<Node> queue = new LinkedList<>();
    queue.offer(root);
    int depth = 0;
    while (!queue.isEmpty()) {
        depth++;
        int size = queue.size();
        while (size > 0) {
            Node node = queue.poll();
            for (Node child : node.children) {
                if (child != null) {
                    queue.offer(child);
                }
            }
            size--;
        }
    }
    return depth;
}

222. 完全二叉树的节点个数【中等】

学习:leetcode题解、 代码随想录—完全二叉树的节点个数

题目链接:222. 完全二叉树的节点个数

题目内容:

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

思路:

1、递归统计

思路: 就是一个递归遍历,最终不断合并左右子节点的数量,最终得到节点个数。

代码:

class Solution {

    //迭代遍历
    public int countNodes(TreeNode root) {
        if(root == null){
            return 0;
        }
        return countNodes(root.left) + countNodes(root.right) + 1;
    }

}

image-20211109184605865

1302. 层数最深叶子节点的和【中等】

题目链接:1302. 层数最深叶子节点的和

题目内容:给你一棵二叉树的根节点 root ,请你返回 层数最深的叶子节点的和 。

思路:

1、前序遍历。使用一个list集合来保存各个层的和并值

复杂度分析:时间复杂度O(n);空间复杂度O(n)

class Solution {
    public int deepestLeavesSum(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        dfs(root, res, 1);
        if (res.size() == 0) {
            return 0;
        }
        return res.get(res.size() - 1);
    }

    public void dfs(TreeNode root, List<Integer> res, int i) {
        if (root != null) {
            if (res.size() < i) {
                res.add(0);
            }
            res.set(i - 1, res.get(i - 1) + root.val);
            if (root.left != null) {
                dfs(root.left, res, i + 1);
            }
            if (root.right != null) {
                dfs(root.right, res, i + 1);
            }
        }
    }
}

2、dfs。取得最大深度,然后去取得深层的合并结果。

复杂度分析:时间复杂度O(n),实际上是2n;空间复杂度O(n)

class Solution {

    private int sum = 0;

    public int deepestLeavesSum(TreeNode root) {
        int maxDepth = maxDepth(root);
        dfs(root, 1, maxDepth);
        return sum;
    }

    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int left = maxDepth(root.left) + 1;
        int right = maxDepth(root.right) + 1;
        return Math.max(left, right);
    }

    public void dfs(TreeNode root, int i, int maxDepth) {
        if (root != null) {
            if (i == maxDepth) {
                sum += root.val;
            }
            dfs(root.left, i + 1, maxDepth);
            dfs(root.right, i + 1, maxDepth);
        }
    }
}

3、一遍遍历,不断的去覆盖最大深度以及结果值。(最优解)

复杂度分析:时间复杂度O(n);空间复杂度O(n)

class Solution {

    private int maxDepth;
    private int res;

    public int deepestLeavesSum(TreeNode root) {
        dfs(root, 1);
        return res;
    }

    public void dfs(TreeNode root, int level) {
        if (root == null) {
            return;
        }
        if (level > maxDepth) {
            maxDepth = level;
            res = 0;
        }
        if (level == maxDepth) {
            res += root.val;
        }
        dfs(root.left, level + 1);
        dfs(root.right, level + 1);
    }
}

image-20220817092726436


124. 二叉树中的最大路径和(图解,代码简洁)【困难】

学习视频:124. 二叉树中的最大路径和(图解,代码简洁)

题目链接:124. 二叉树中的最大路径和

题目内容:路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和 。

思路:

1、dfs递归。

复杂度分析:时间复杂度O(n);空间复杂度O(n)

class Solution {

    private int maxPath = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
        maxGain(root);
        return maxPath;
    }

    public int maxGain(TreeNode root) {
        if (root == null) {
            return 0;
        }
        //获取到左右最大的路径和
        int left = maxGain(root.left);//获取到左边最大的路径
        int right = maxGain(root.right);//获取到右边最大的路径
        //更新当前路径的最大值
        maxPath = Math.max(maxPath, root.val + left + right);

        //返回当前最大的一个路径和(1、只包含自己结点本身。2、左+自己。3、右+自己)
        int res = root.val + Math.max(0, Math.max(left, right));
        //考虑结点自己本身是小于0的情况,那么返回0
        return res < 0 ? 0 : res; 
    }
}

image-20220817102437056

评论区请在客户端页面查看