⛔⛔⛔数据结构——树树树树树树树树树树树树树树树树

文章目录

1 递归

树的高度深度⭐⭐⭐

104最大深度

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

111最小路径——最浅深度

方法一:递归DFS

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

方法二:BFS

int minDepth(TreeNode root) {
    if (root == null) return 0;
    Queue<TreeNode> q = new LinkedList<>();
    q.offer(root);
    // root 本身就是一层,depth 初始化为 1
    int depth = 1;

    while (!q.isEmpty()) {
        int sz = q.size();
        /* 将当前队列中的所有节点向四周扩散 */
        for (int i = 0; i < sz; i++) {
            TreeNode cur = q.poll();
            /* 判断是否到达终点 */
            if (cur.left == null && cur.right == null) 
                return depth;
            /* 将 cur 的相邻节点加入队列 */
            if (cur.left != null)
                q.offer(cur.left);
            if (cur.right != null) 
                q.offer(cur.right);
        }
        /* 这里增加步数 */
        depth++;
    }
    return depth;
}

110判断平衡树——深度

高度差小于等于一,遍历节点的深度,判断差值

private boolean result = true;

public boolean isBalanced(TreeNode root) {
    maxDepth(root);
    return result;
}

public int maxDepth(TreeNode root) {
    if (root == null) return 0;
    int l = maxDepth(root.left);
    int r = maxDepth(root.right);
    if (Math.abs(l - r) > 1) result = false;
    return 1 + Math.max(l, r);
}

543树的路径和——某点的左右子树的深度和

private int max = 0;

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

private int depth(TreeNode root) {
    if (root == null) return 0;
    int leftDepth = depth(root.left);
    int rightDepth = depth(root.right);
    max = Math.max(max, leftDepth + rightDepth);
    return Math.max(leftDepth, rightDepth) + 1;
}

226翻转树左右子树——自上而下递归

public TreeNode invertTree(TreeNode root) {
    if (root == null) return null;
    TreeNode left = root.left;  // 后面的操作会改变 left 指针,因此先保存下来
    root.left = invertTree(root.right);
    root.right = invertTree(left);
    return root;
}

617合并两个树——自上而下构建新结点递归合并

public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
    if (t1 == null && t2 == null) return null;
    if (t1 == null) return t2;
    if (t2 == null) return t1;
    TreeNode root = new TreeNode(t1.val + t2.val);
    root.left = mergeTrees(t1.left, t2.left);
    root.right = mergeTrees(t1.right, t2.right);
    return root;
}

112 判断路径和==某个数?——从上往下,减去结点值递归往下找

base case
1.未找到:访问到了空结点
2.找到了:.当前结点值正好等于余数,且是叶子结点。

public boolean hasPathSum(TreeNode root, int sum) {
    if (root == null) return false;
    if (root.left == null && root.right == null && root.val == sum) return true;
    return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}

437统计路径和==某个数?(不一定以 root 开头)——遍历所有结点的时候都认为是从根结点开始,然后把路径数加到一起

public int pathSum(TreeNode root, int sum) {
    if (root == null) return 0;
    int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
    return ret;
}

private int pathSumStartWithRoot(TreeNode root, int sum) {
    if (root == null) return 0;
    int ret = 0;
    if (root.val == sum) ret++;
    ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val);
    return ret;
}

687 相同节点值的最大路径长度——路径上的值判断

class Solution {
    private int  maxdis =0;
    //dfs从底向上寻找
    public int longestUnivaluePath(TreeNode root) {
        longdistance(root);
        return maxdis;
    }
//    定义一个函数,从低向上判断相同结点相连的左右结点之和(不包括根节点所以不用减掉一)
//
    private int longdistance(TreeNode root){
        if(root == null) return 0;
        int l = longdistance( root.left);
        int r  = longdistance(root.right);
        int ldep = root.left!=null&&root.left.val==root.val?l+1:0;
        int rdep =root.right!=null&&root.right.val==root.val?r+1:0;
        //记录最大深度
        maxdis=Math.max(maxdis,rdep+ldep);
        //返回当前节点的深度,左右两边的最大长度
        return Math.max(ldep,rdep);
    }

}

337打家劫舍——1.动态规划. 2.间隔遍历

class Solution {
    public int rob(TreeNode root) {
        //间接dfs 累积之和的最大值
        if(root ==null) return 0;
//        记录当前结点加上左右树累积的最大值
        int val =root.val;
//        间接左子树累积最大值
        if(root.left!=null) val+=rob(root.left.left)+rob(root.left.right);
//        间接右子树累积最大值
        if(root.right!=null) val+=rob(root.right.left)+rob(root.right.right);
//        当前结点不累计。记录从下个结点才开始累积的最大值
        int val2=rob(root.left)+rob(root.right);
        //看看哪个最大,返回当前结点累积的最大值
        return Math.max(val,val2);
    }
}

671第二小的结点——找左右子树中最小的

public int findSecondMinimumValue(TreeNode root) {
    if (root == null) return -1;
    if (root.left == null && root.right == null) return -1;
    int leftVal = root.left.val;
    int rightVal = root.right.val;
    if (leftVal == root.val) leftVal = findSecondMinimumValue(root.left);
    if (rightVal == root.val) rightVal = findSecondMinimumValue(root.right);
    if (leftVal != -1 && rightVal != -1) return Math.min(leftVal, rightVal);
    if (leftVal != -1) return leftVal;
    return rightVal;
}

同树判断

核心代码

private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
    if (t == null && s == null) return true;
    if (t == null || s == null) return false;
    if (t.val != s.val) return false;
    return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
}

变形1:572子树判断——遍历所有结点判断同树

public boolean isSubtree(TreeNode s, TreeNode t) {
    if (s == null) return false;
    //遍历结点
    return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);
}

private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
    if (t == null && s == null) return true;
    if (t == null || s == null) return false;
    if (t.val != s.val) return false;
    return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
}

变形2:101 树的对称——递归判断左子树和右子树是否相同

public boolean isSymmetric(TreeNode root) {
    if (root == null) return true;
    return isSymmetric(root.left, root.right);
}

private boolean isSymmetric(TreeNode t1, TreeNode t2) {
    if (t1 == null && t2 == null) return true;
    if (t1 == null || t2 == null) return false;
    if (t1.val != t2.val) return false;
    return isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left);
}

构造二叉树

105前序中序构造二叉树——前序找根,递归划分左右子树

不错的题解
在这里插入图片描述

public class ti_105 {
    HashMap<Integer,Integer> memo= new HashMap<>();
    int[] post;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        for(int i=0;i<inorder.length;i++){//中序遍历存放位置索引,便于取索引
            memo.put(inorder[i],i);
        }
        post = preorder;
        TreeNode root = buildTree(0,inorder.length-1,0,post.length-1);
        return root;
    }
    //递归建树
    //前序遍历[preStart,preEnd] 中序遍历[inStart,inEnd]
    public TreeNode buildTree(int preStart,int preEnd,int inStart,int inEnd){
        if(inStart>inEnd || preStart > preEnd){                   //叶子结点
            return null;
        }
        int node =post[preStart]; //前序第一个是根节点
        int index = memo.get(node);
        TreeNode root =new TreeNode(node);
        int leftLen = index-inStart;
        root.left = buildTree(preStart+1,preStart+leftLen,inStart,index-1);//前序的去掉前面的
        root.right = buildTree(preStart+leftLen+1,preEnd,index+1,inEnd);//
        return root;//返回当前结点给上一级的left或者right
    }
}

106中序与后序构造二叉树——从后序找根结点,递归划分中序的左右子树

在这里插入图片描述

不错的题解

public class ti_106 {
    HashMap<Integer,Integer> memo= new HashMap<>();
    int[] post;
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        for(int i=0;i<inorder.length;i++){//中序遍历存放位置索引
            memo.put(inorder[i],i);
        }
        post = postorder;
        TreeNode root = buildTree(0,inorder.length-1,0,post.length-1);
        return root;
    }
    //递归建树前序遍历
    //中序[inStart,inEnd]  后序[postStart,postEnd]
    public TreeNode buildTree(int inStart,int inEnd,int postStart,int postEnd){
        if(inStart>inEnd || postStart>postEnd){                   //叶子结点
            return null;
        }
        int node = post[postEnd];
        int index = memo.get(node);
        TreeNode root =new TreeNode(node);
        int leftLen = index - inStart;
        root.left = buildTree(inStart,index-1,postStart,postStart+leftLen-1);//ri-is-1为左子树长度
        root.right = buildTree(index+1,inEnd,postStart+leftLen,postEnd-1);//pe-1去掉最后的根结点
        return root;//返回当前结点给上一级的left或者right
    }
}

654构造最大二叉树——构建当前结点+递归构建左右结点

在这里插入图片描述

public class ti_654 {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        return build(nums,0,nums.length-1);
    }
    public TreeNode build(int[] nums,int start,int end){
        if(start>end){
            return null;
        }
        int maxValue = Integer.MIN_VALUE;
        //可优化,寻找最大值
        int index = -1;
        for(int i = start; i <= end ;i++){
            if(nums[i]>maxValue){
                maxValue = nums[i];
                index = i;
            }
        }
        TreeNode root = new TreeNode(maxValue);
        root.left = build(nums,start,index-1);
        root.right = build(nums,index+1,end);
        return root;
    }
}

2 前中后序遍历

2.1 递归实现

前序遍历框架

public void dfs(TreeNode root) {
    visit(root.val);
    dfs(root.left);
    dfs(root.right);
}

中序遍历框架

public void dfs(TreeNode root) {
    dfs(root.left);
    visit(root.val);
    dfs(root.right);
}

后序遍历框架

public void dfs(TreeNode root) {
    dfs(root.left);
    dfs(root.right);
    visit(root.val);
}

2.2 非递归实现

144前序遍历框架⭐⭐

构建栈,根左右,先弹出根结点,然后将右结点先放,最后弹出。
弹出记得保存。

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        if (node == null) continue;
        ret.add(node.val);
        stack.push(node.right);  // 先右后左,保证左子树先遍历
        stack.push(node.left);
    }
    return ret;
}

145中序遍历框架

左子结点存到底,弹出记得存入右子结点。
弹出时记得保存。

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    if (root == null) return ret;
    Stack<TreeNode> stack = new Stack<>();
    TreeNode cur = root;
    while (cur != null || !stack.isEmpty()) {
        while (cur != null) {
            stack.push(cur);
            cur = cur.left;
        }
        TreeNode node = stack.pop();
        ret.add(node.val);
        cur = node.right;
    }
    return ret;
}

94后序遍历框架

通过前序遍历变形得到,后序遍历为左右根,反过来是根右左。

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> ret = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        if (node == null) continue;
        ret.add(node.val);
        stack.push(node.left);
        stack.push(node.right);
    }
    Collections.reverse(ret);
    return ret;
}

3 层序遍历

层序遍历队列实现框架⭐⭐⭐

BFS实现,队列实现,先入先出,从左到右的层序遍历。

public void bfs(TreeNode root) {
    if (root == null) return;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        int cnt = queue.size();
        for (int i = 0; i < cnt; i++) {
            TreeNode node = queue.poll();
            //存储操作   
            if (node.left != null) queue.add(node.left);
            if (node.right != null) queue.add(node.right);
        }
    }
}

BFS框架

// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
    Queue<Node> q; // 核心数据结构
    Set<Node> visited; // 避免走回头路
    q.offer(start); // 将起点加入队列
    visited.add(start);
    int step = 0; // 记录扩散的步数
    while (q not empty) {
        int sz = q.size();
        /* 将当前队列中的所有节点向四周扩散 */
        for (int i = 0; i < sz; i++) {
            Node cur = q.poll();
            /* 划重点:这里判断是否到达终点 */
            if (cur is target)
                return step;
            /* 将 cur 的相邻节点加入队列 */
            for (Node x : cur.adj())
                if (x not in visited) {
                    q.offer(x);
                    visited.add(x);
                }
        }
        /* 划重点:更新步数在这里 */
        step++;
    }
}

637每层结点的平均值——层序遍历适当修改

public List<Double> averageOfLevels(TreeNode root) {
    List<Double> ret = new ArrayList<>();
    if (root == null) return ret;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        int cnt = queue.size();
        double sum = 0;
        for (int i = 0; i < cnt; i++) {
            TreeNode node = queue.poll();
            sum += node.val;
            if (node.left != null) queue.add(node.left);
            if (node.right != null) queue.add(node.right);
        }
        ret.add(sum / cnt);
    }
    return ret;
}

4 序列化与反序列化

详解

4.1 前序遍历序列化和反序列化

在这里插入图片描述

序列化成数组
->res = [1,2,-1,4,-1,-1,3,-1,-1]

LinkedList<Integer> res;
void traverse(TreeNode root) {
    if (root == null) {
        // 暂且用数字 -1 代表空指针 null
        res.addLast(-1);
        return;
    }

    /****** 前序遍历位置 ******/
    res.addLast(root.val);
    /***********************/

    traverse(root.left);
    traverse(root.right);
}

序列化成字符串
->sb=“1,2,#,4,#,#,3,#,#,”

String SEP = ",";
String NULL = "#";

/* 主函数,将二叉树序列化为字符串 */
String serialize(TreeNode root) {
    StringBuilder sb = new StringBuilder();
    serialize(root, sb);
    return sb.toString();
}
/* 辅助函数,将二叉树存入 StringBuilder */
void serialize(TreeNode root, StringBuilder sb) {
    if (root == null) {
        sb.append(NULL).append(SEP);
        return;
    }

    /****** 前序遍历位置 ******/
    sb.append(root.val).append(SEP);
    /***********************/

    serialize(root.left, sb);
    serialize(root.right, sb);
}

反序列化

  • 无空指针信息:至少要得到前、中、后序遍历中的两种才能还原二叉树。
  • 包含空指针的信息:node 列表就可以还原二叉树。

反序列化思路:先找根构建当前结点,递归构建左右子树

/* 主函数,将字符串反序列化为二叉树结构 */
TreeNode deserialize(String data) {
    // 将字符串转化成列表
    LinkedList<String> nodes = new LinkedList<>();
    for (String s : data.split(SEP)) {
        nodes.addLast(s);
    }
    return deserialize(nodes);
}

/* 辅助函数,通过 nodes 列表构造二叉树 */
TreeNode deserialize(LinkedList<String> nodes) {
    if (nodes.isEmpty()) return null;

    /****** 前序遍历位置 ******/
    // 列表最左侧就是根节点
    String first = nodes.removeFirst();
    //回溯时机,返回上一层去构建右子树
    if (first.equals(NULL)) return null;
    TreeNode root = new TreeNode(Integer.parseInt(first));
    /***********************/
    
    root.left = deserialize(nodes);
    root.right = deserialize(nodes);
    return root;
}

4.2 后序遍历的序列化与反序列化

序列化

String SEP = ",";
String NULL = "#";
/* 辅助函数,将二叉树存入 StringBuilder */
void serialize(TreeNode root, StringBuilder sb) {
    if (root == null) {
        sb.append(NULL).append(SEP);
        return;
    }

    serialize(root.left, sb);
    serialize(root.right, sb);

    /****** 后序遍历位置 ******/
    sb.append(root.val).append(SEP);
    /***********************/
}

反序列化

找根结点,递归构建左右子树从后往前在 nodes 列表中取元素,一定要先构造 root.right 子树,后构造 root.left 子树。
在这里插入图片描述

/* 主函数,将字符串反序列化为二叉树结构 */
TreeNode deserialize(String data) {
    LinkedList<String> nodes = new LinkedList<>();
    for (String s : data.split(SEP)) {
        nodes.addLast(s);
    }
    return deserialize(nodes);
}
/* 辅助函数,通过 nodes 列表构造二叉树 */
TreeNode deserialize(LinkedList<String> nodes) {
    if (nodes.isEmpty()) return null;
    // 从后往前取出元素
    String last = nodes.removeLast();
    if (last.equals(NULL)) return null;
    TreeNode root = new TreeNode(Integer.parseInt(last));
    // 限构造右子树,后构造左子树
    root.right = deserialize(nodes);
    root.left = deserialize(nodes);

    return root;
}

4.3 中序遍历的序列化与反序列化

序列化

/* 辅助函数,将二叉树存入 StringBuilder */
void serialize(TreeNode root, StringBuilder sb) {
    if (root == null) {
        sb.append(NULL).append(SEP);
        return;
    }

    serialize(root.left, sb);
    /****** 中序遍历位置 ******/
    sb.append(root.val).append(SEP);
    /***********************/
    serialize(root.right, sb);
}

反序列化行不通,确定不了根节点!!

4.4 层序遍历的序列化与反序列化

序列化

String SEP = ",";
String NULL = "#";

/* 将二叉树序列化为字符串 */
String serialize(TreeNode root) {
    if (root == null) return "";
    StringBuilder sb = new StringBuilder();
    // 初始化队列,将 root 加入队列
    Queue<TreeNode> q = new LinkedList<>();
    q.offer(root);

    while (!q.isEmpty()) {
        TreeNode cur = q.poll();

        /* 层级遍历代码位置 */
        if (cur == null) {
            sb.append(NULL).append(SEP);
            continue;
        }
        sb.append(cur.val).append(SEP);
        /*****************/

		//null也要加入奥
        q.offer(cur.left);
        q.offer(cur.right);
    }

    return sb.toString();
}

反序列化

  • 拿出时候构建该结点
  • 压入左右子结点,null不压入,依次读取
/* 将字符串反序列化为二叉树结构 */
TreeNode deserialize(String data) {
    if (data.isEmpty()) return null;
    String[] nodes = data.split(SEP);
    // 第一个元素就是 root 的值
    TreeNode root = new TreeNode(Integer.parseInt(nodes[0]));

    // 队列 q 记录父节点,将 root 加入队列
    Queue<TreeNode> q = new LinkedList<>();
    q.offer(root);

    for (int i = 1; i < nodes.length; ) {
        // 队列中存的都是父节点
        TreeNode parent = q.poll();
        // 父节点对应的左侧子节点的值
        String left = nodes[i++];
        if (!left.equals(NULL)) {
            parent.left = new TreeNode(Integer.parseInt(left));
            q.offer(parent.left);
        } else {
            parent.left = null;
        }
        // 父节点对应的右侧子节点的值
        String right = nodes[i++];
        if (!right.equals(NULL)) {
            parent.right = new TreeNode(Integer.parseInt(right));
            q.offer(parent.right);
        } else {
            parent.right = null;
        }
    }
    return root;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zkFun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值