数据结构刷题

刷题

二叉树

综上,遇到一道二叉树的题目时的通用思考过程是:

1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse 函数配合外部变量来实现。

2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值

3、无论使用哪一种思维模式,你都要明白二叉树的每一个节点需要做什么,需要在什么时候(前中后序)做

/**
 * 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;
 *     }
 * }
 */

Leetcode-94:二叉树的中序遍历

​ 使用的实现二叉树的中序遍历操作

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        Stack<TreeNode> stack=new Stack();
        List<Integer> result=new ArrayList<>();
        if(root==null) return result;
        TreeNode cur=root;
        while(cur!=null || !stack.isEmpty()){//这里为什么要cur!=null? 因为栈一开始为空无法进入循环
            if(cur!=null){
                stack.push(cur);
                cur=cur.left;//如果指针指向的左节点不为空,则一直往左节点指
            }else{
                cur = stack.pop();//这里要注意就是要让cur指向stack.pop()的返回值,否则cur会空指针异常,也是让指针上移
                result.add(cur.val);
                cur = cur.right;//指针往右节点指
            }
        }
        return result;
    }
}

Leetcode-543:二叉树的直径

​ 求二叉树最大深度的变式,也是理解二叉树中后序位置的含义,也就是二叉树分解问题的解题思路

class Solution {
    // 记录最大直径的长度
    int maxDiameter = 0; 

    public int diameterOfBinaryTree(TreeNode root) {
        diameter(root);
        return maxDiameter;
    }

    //递归函数:求二叉树最大深度的同时求二叉树的最大直径
    private int diameter(TreeNode root){
        if(root==null) return 0;

        int left=diameter(root.left);
        int right=diameter(root.right);

        //后序位置
        int maxlength=left+right;//左子树节点个数+右子树节点个数
        maxDiameter=Math.max(maxDiameter,maxlength);

        return 1+Math.max(left,right);//算出左右子树最大深度并返回
    }
}

Leetcode-226:翻转二叉树

​ 就像开头所说的,二叉树的题目,两个大方向,是否可以遍历整颗二叉树得到答案,另一种就是通过子树(子问题)解决推导出原问题的答案。

​ 并且还有思考单个节点需要做什么:单独抽出一个节点,需要让它做什么?让它把自己的左右子节点交换一下。

​ 第一种方向的解法:

class Solution {
    public TreeNode invertTree(TreeNode root) {
        invert(root);
        return root;
    }

    //定义一个递归函数:遍历整个二叉树,让每个节点的左右子树左右调换
    public void invert(TreeNode root){
        if(root==null) return;

        //前序位置操作,交换该节点的左子树和右子树,注意这里是交换整颗子树,不是单个节点(后序位置也可以)
        TreeNode node =root.left;
        root.left=root.right;
        root.right=node;

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

​ 第二种方向的解法:你要给递归函数一个定义,相信它能完成你给的定义,具体的递归细节不要去纠结。

class Solution {
    public TreeNode invertTree(TreeNode root) {
        root=invert(root);
        return root;
    }

    //定义一个递归函数:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点
    public TreeNode invert(TreeNode root){
        if(root==null) return null;

        TreeNode left=invert(root.left);//先翻转root根节点的左子树
        TreeNode right=invert(root.right);//再翻转root根节点的右子树
		
        //后序位置:最后将root根节点的左子树与翻转后的右子树调换,同理,最后返回根节点root
        root.left=right;
        root.right=left;
        return root;
    }
}

Leetcode-102:层序遍历

​ 直接上代码(模板):核心就是外循环从上到下对整个树进行遍历,内循环就是对树的每一层从左到右进行遍历。

class Solution {
    List<List<Integer>> result=new ArrayList<List<Integer>>();

    public List<List<Integer>> levelOrder(TreeNode root) { 
        level(root);
        return result;
    }

    public void level(TreeNode root){
        if(root==null) return;

        Queue<TreeNode> queue=new LinkedList<>();
        queue.offer(root);//入栈根节点
        
        //外层while循环从上到下遍历
        while(!queue.isEmpty()){
            List<Integer> res=new ArrayList<>();
            int size=queue.size();//统计队列中元素个数,也就是统计树的每一行有多少个节点
            
            //内层for循环遍历树每行的从左到右
            for(int i=0;i<size;i++){

                TreeNode node=queue.poll();
                res.add(node.val);
                
                if(node.left!=null){
                    queue.offer(node.left);
                }
                if(node.right!=null){
                    queue.offer(node.right);
                }
            }
            result.add(res);
        }
    }
}

Leetcode_114:二叉树展开链表

​ 大问题分解小问题的思路:定义一个递归函数,让它的作用就是输入节点 root,然后 root 为根的二叉树就会被拉平为一条链表,具体如何拉平,就是用两个指针分别保存root节点左右子树信息,将左子树覆盖到右子树,最后将右子树插入末尾即可。

class Solution {
    public void flatten(TreeNode root) {
        traverse(root);
    }
    // 定义:输入节点 root,然后 root 为根的二叉树就会被拉平为一条链表
    public void traverse(TreeNode root){
        if(root==null) return;

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

        //后序位置
        //先用指针记录(保存)左右子树位置
        TreeNode right=root.right;
        TreeNode left=root.left;

        root.left=null;//让左子树置空,由于前面指针保存了左子树的位置,所以左子树信息不会丢失
        root.right=left;//让左子树覆盖右子树
        
        //定义一个指针指向root
        TreeNode p=root;
        while(p.right!=null){
            p=p.right;//一直指向右子树末尾
        }
        p.right=right;//将原来的右子树插入新的右子树末尾即可
    }
}

Leetcode-654:最大二叉树

​ 构造树的分解问题:构造根节点+构造根节点的左子树+构造根节点的右子树

​ 从这道题中我们可以学到如果要控制数组的索引时,我们可以多封装一个函数,然后用形参的方式去控制数组索引。例如下面的build(int[] nums,int l,int r)l控制数组循环条件的左边界,r控制数组循环条件的右边界,for(int i=l;i<=r;i++),来达到逻辑上的在同一个数组上寻找"最大值"。

class Solution {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        return build(nums,0,nums.length-1);
    }
    public TreeNode build(int[] nums,int l,int r){
        //这里是l>r而不是l>=r
        if(l>r) return null;

        int Max=Integer.MIN_VALUE;
        int index=-1;

        //遍历数组求最大值作为根节点
        //for(int i=0;i<nums.length;i++){//错误写法:
        //正确写法:l和r记录了我们每次循环nums数组的起始值和结束值,如果i从0开始nums.length结束那么最大值就永远只有一个会造成栈溢出
        for(int i=l;i<=r;i++){
            if(nums[i]>Max){
                index=i;
                Max=nums[i];
            }
        }

        //构造二叉树的根节点
        TreeNode root=new TreeNode(Max);

        //递归构造二叉树的左右子树
        root.left=build(nums,l,index-1);
        root.right=build(nums,index+1,r);

        return root;
    }
}

Leetcode-106:从中序与后序遍历序列构造二叉树

​ 构造树的分解问题:构造根节点+构造根节点的左子树+构造根节点的右子树

​ 构造左子树和右子树中索引范围如图所示:image-20220921145640248

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        return build(inorder,0,inorder.length-1,postorder,0,postorder.length-1);
    }

    public TreeNode build(int[] in,int il,int ir,
                          int[] post,int pl,int pr){

        if(pl>pr) return null;

        int index=-1;
        int rootVal=post[pr];//后序遍历最后一个即是根节点的值

        for(int i=il;i<=ir;i++){
            if(in[i]==rootVal){
                index=i;//找到后序遍历中根节点在中序遍历中的位置索引
                break;
            }
        }
        //创建根节点
        TreeNode root=new TreeNode(rootVal);
        int leftSize=index-il;//左子树节点个数
        //创建根节点的左子树
        root.left=build(in,il,index-1,post,pl,pl+leftSize-1);
        //创建根节点的右子树
        root.right=build(in,index+1,ir,post,pl+leftSize,pr-1);
        return root;
    }
}
posted @ 2023-06-06 11:40  stepForward-  阅读(31)  评论(0)    收藏  举报
@format