LeetCode_二叉树刷题笔记4(java)

842 阅读8分钟

这个系列主要是记录我刷题的过程。重点是每一类型题解法的循序渐进,按着这个顺序基本每一题都能做出来。而不是某一题的解法,所以适合打算大量刷题的人参考。二叉树相关的刷题顺序是参考 代码随想录

LeetCode_二叉树刷题笔记4(java)

LeetCode_二叉树刷题笔记3(Java)

LeetCode_二叉树刷题笔记2(java)

LeetCode_二叉树刷题笔记1(java)

下面几道还是跟二叉搜索树相关的题目。

image-20210222161312700

二叉搜索树

701. 二叉搜索树中的插入操作

难度中等159

给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果

示例 1:

img

输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
解释:另一个满足题目要求可以通过的树是:

示例 2:

输入:root = [40,20,60,10,30,50,70], val = 25
输出:[40,20,60,10,30,50,70,null,null,25]

示例 3:

输入:root = [4,2,7,1,3,null,null,null,null,null,null], val = 5
输出:[4,2,7,1,3,5]

提示:

  • 给定的树上的节点数介于 010^4 之间
  • 每个节点都有一个唯一整数值,取值范围从 010^8
  • -10^8 <= val <= 10^8
  • 新值和原始二叉搜索树中的任意节点值都不同

思路

题目提示中:新值和原始二叉搜索树中的任意节点值都不同。这样在不改动原有的树结构的情况下,只需要将小于当前节点的值放左边,大于当前节点的放右边,同时只有当前节点是左右有一个节点为空时,才能插入节点。

/**
 * 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 TreeNode insertIntoBST(TreeNode root, int val) {
      	//此时可以返回新节点
        if(root==null)return new TreeNode(val);
				//往左边插入
        if(root.val>val){
            TreeNode left = insertIntoBST(root.left,val);
            if(left!=null) root.left=left;
        }
      	//往右边插入
        if(root.val<val){
            TreeNode right=insertIntoBST(root.right,val);
            if(right!=null) root.right=right;
        }

        return  root;
        
    }
}

450. 删除二叉搜索树中的节点

难度中等395

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

说明: 要求算法时间复杂度为 O(h),h 为树的高度。

示例:

root = [5,3,6,2,4,null,7]
key = 3

    5
   / \
  3   6
 / \   \
2   4   7

给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。

一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。

    5
   / \
  4   6
 /     \
2       7

另一个正确答案是 [5,2,6,null,4,null,7]。

    5
   / \
  2   6
   \   \
    4   7

思路

这题大思路不难,很多细节。

  • 找到目标节点(递归,前序遍历)
  • 将目前节点的前置节点作为新的根节点(使用后置节点也是可以的)
  • 删除目标节点

细节在源码备注中:

/**
 * 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 TreeNode deleteNode(TreeNode root, int key) {
        if(root==null)return null;
        //找到了要删除的结点
        if (root.val == key) {
            //目标节点有左右子节点
            if(root.left!=null&&root.right!=null){
                //找到目标节点的前置接节点
                if(root.left.right!=null){
                    TreeNode node =root.left.right;
                    TreeNode preNode =root.left;
                    //不停的找右节点
                    while (node.right!=null){
                        preNode=node;
                        node=node.right;
                    }
                    //前置节点的左节点不为空,
                    if(node.left!=null){
                        preNode.right=node.left;
                        node.left =root.left;
                        node.right = root.right;
                        return  node;
                    }else{
                        preNode.right =  null;
                        node.left =root.left;
                        node.right = root.right;
                        return  node;
                    }
                }else{
                    //如果前置节点就是该节点的左节点,直接用该左节点作为根节点
                    root.left.right=root.right;
                    TreeNode newNode =root.left;
                    root=null;
                    return  newNode;
                }

            }
            //需要返回一个 删除root后,重新构建为BST的root节点
            if(root.left==null&&root.right==null){
                //目标节点是叶子节点,直接删除即可
                return  root=null;
            }
            //返回不为null的子节点
            return root.left!=null?root.left:root.right;
        }

        if (root.val > key) {
            root.left= deleteNode(root.left, key);
        }
        if (root.val < key) {
            root.right = deleteNode(root.right, key);
        }
        return root;


    }

}

669. 修剪二叉搜索树

难度中等351

给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树不应该改变保留在树中的元素的相对结构(即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在唯一的答案。

所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。

示例 1:

img

输入:root = [1,0,2], low = 1, high = 2
输出:[1,null,2]

示例 2:

img

输入:root = [3,0,4,null,2,null,null,1], low = 1, high = 3
输出:[3,2,null,1]

示例 3:

输入:root = [1], low = 1, high = 2
输出:[1]

示例 4:

输入:root = [1,null,2], low = 1, high = 3
输出:[1,null,2]

示例 5:

输入:root = [1,null,2], low = 2, high = 4
输出:[2]

提示:

  • 树中节点数在范围 [1, 104]
  • 0 <= Node.val <= 104
  • 树中每个节点的值都是唯一的
  • 题目数据保证输入是一棵有效的二叉搜索树
  • 0 <= low <= high <= 104

思路

还是得用递归,从上往下前序遍历。如果不符合[L,R],删除即可。

需要注意的一个节点的值小于L,但是它的右节点是可能大约等于L的。

同理,某节点的值大于R,但是它的左节点可能小于等于R的。这类节点不能都删除了

/**
 * 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 TreeNode trimBST(TreeNode root, int low, int high) {
        if(root==null) return null;
        if(root.val<low){
          	//该节点的右节点可能>=low
            return trimBST(root.right,low,high);
        }
        if(root.val>high){
          	//该节点的左子节点坑你<=heig
            return trimBST(root.left,low,high);
        }
		
        root.left = trimBST(root.left,low,high);
        root.right = trimBST(root.right,low,high);
        return  root;

    }
}

108. 将有序数组转换为二叉搜索树

难度简单701

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。

高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。

示例 1:

img

输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:

示例 2:

img

输入:nums = [1,3]
输出:[3,1]
解释:[1,3][3,1] 都是高度平衡二叉搜索树。

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums严格递增 顺序排列

思路

/**
 * 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 TreeNode sortedArrayToBST(int[] nums) {
            return  sortedArrayToBST(0,nums.length-1,nums);
    }
    //[start,end]
    private  TreeNode sortedArrayToBST(int start,int end,int[] nums){
        if(start>end) return null;
        //中间 注意+start
        int mid = start+((end-start)>>1);
        TreeNode root=new TreeNode(nums[mid]);
        root.left = sortedArrayToBST(start,mid-1,nums);
        root.right = sortedArrayToBST(mid+1,end,nums);

        return root;
    }
}

538. 把二叉搜索树转换为累加树

难度中等478

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

提醒一下,二叉搜索树满足下列约束条件:

  • 节点的左子树仅包含键 小于 节点键的节点。
  • 节点的右子树仅包含键 大于 节点键的节点。
  • 左右子树也必须是二叉搜索树。

**注意:**本题和 1038: leetcode-cn.com/problems/bi… 相同

示例 1:

img

输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]

示例 2:

输入:root = [0,null,1]
输出:[1,null,1]

示例 3:

输入:root = [1,0,2]
输出:[3,3,2]

示例 4:

输入:root = [3,2,4,1]
输出:[7,9,4,10]

提示:

  • 树中的节点数介于 0104 之间。
  • 每个节点的值介于 -104104 之间。
  • 树中的所有值 互不相同
  • 给定的树为二叉搜索树。

思路

这道题看上去挺唬人的。不过只要搞清楚什么是累加树,已经累加树的值是怎么计算得来的,就很简单了。

如下图,按照 右-->中-->左的顺序 累加即可:

image-20210222150516293
/**
 * 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 {
  	//记录前一个节点的值
    int preValue =0;
    public TreeNode convertBST(TreeNode root) {

        //什么是累加树?
        //累加树的值是怎么计算的?
        if(root==null) return null;
        convertBST(root.right);
        root.val+=preValue;
        preValue=root.val;
        convertBST(root.left);

        return  root;

    }
}

99. 恢复二叉搜索树

难度困难415

给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。

**进阶:**使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?

示例 1:

img

输入:root = [1,3,null,null,2]
输出:[3,1,null,null,2]
解释:3 不能是 1 左孩子,因为 3 > 1 。交换 13 使二叉搜索树有效。

示例 2:

img

输入:root = [3,1,4,null,null,2]
输出:[2,1,4,null,null,3]
解释:2 不能在 3 的右子树中,因为 2 < 3 。交换 23 使二叉搜索树有效。

提示:

  • 树上节点的数目在范围 [2, 1000]
  • -231 <= Node.val <= 231 - 1

思路

首先,得知道 二叉搜索树中序遍历会是个升序

如果该树是有问题的,那么这个升序中就是有 逆序对。而且题目中指出只有一对逆序对。

所以值需要找到逆序对,然后交换值即可:

/**
 * 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 TreeNode prev;
    //第一个错误结点
    private TreeNode first;
    //第二个错误结点
    private TreeNode second;
    public void recoverTree(TreeNode root) {
        findWrongNodes(root);  
        //交换两个错误结点的值
        int temp=second.val;
        second.val=first.val;
         first.val=temp;

    }

    private void findWrongNodes(TreeNode root){
        if(root==null) return;
      	//注意是中序遍历
        findWrongNodes(root.left);
        if(prev!=null&&prev.val>root.val){
            //出现逆序对
            //第二错误结点,是最后一个逆序对中较小的那个结点
            second=root;
            //第一个错误结点,是第一个逆序对中较大的那个结点
            if(first!=null) return;
            first=prev;
        }
        prev=root;
        findWrongNodes(root.right);
    }
}

333. 最大 BST 子树

难度中等81

给定一个二叉树,找到其中最大的二叉搜索树(BST)子树,并返回该子树的大小。其中,最大指的是子树节点数最多的。

**二叉搜索树(BST)**中的所有节点都具备以下属性:

  • 左子树的值小于其父(根)节点的值。
  • 右子树的值大于其父(根)节点的值。

注意:

  • 子树必须包含其所有后代。

进阶:

  • 你能想出 O(n) 时间复杂度的解法吗?

示例 1:

img

输入:root = [10,5,15,1,8,null,7]
输出:3
解释:本例中最大的 BST 子树是高亮显示的子树。返回值是子树的大小,即 3 。

示例 2:

输入:root = [4,2,7,2,3,5,null,2,null,null,null,null,null,1]
输出:2

提示:

  • 树上节点数目的范围是 [0, 104]
  • -104 <= Node.val <= 104

思路

/**
 * 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 int largestBSTSubtree(TreeNode root) {
        return (root==null)?0:getInfo(root).size;
    }
   //获取当前node为根节点的二叉树的最大BST子树的信息
    private Info getInfo(TreeNode node){
        if(node==null)return null;
        //左子树最大BST信息
        Info li=getInfo(node.left);
        //右子树最大BST信息
        Info ri=getInfo(node.right);

        int leftBstSize=-1,rightBstSize=-1,min=node.val,max=node.val;

      //左子树没有BST,或者左子树就是BST
        if(li==null){
            leftBstSize=0;
        }else if(li.root==node.left&&node.val>li.max){
            leftBstSize=li.size;
            min=li.min;
        }
			//右子树没有BST,或者右子树就是BST
        if(ri==null){
            rightBstSize=0;
        }else if(ri.root==node.right&&node.val<ri.min){
            rightBstSize=ri.size;
            max=ri.max;
        }
        //以node为根节点的二叉树是BST
        if(leftBstSize>=0&&rightBstSize>=0){
        return new Info(node,1+leftBstSize+rightBstSize,min,max);
        }
    
        //以node为根节点的二叉树不是BST
        if(li!=null&&ri!=null)return (li.size>ri.size)?li:ri;

        return(li!=null)?li:ri;

    }
   private static class Info{

       public TreeNode root;
       public int size;
       public int min;
       public int max;

    public Info(TreeNode root,int size,int min,int max){
        this.root= root;
        this.size=size;
        this.min=min;
        this.max=max;
    }
   }
}

最小公共区域

235. 二叉搜索树的最近公共祖先

难度简单533

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

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

img

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6

示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。

思路

首先得知道二叉树搜索树的特性,当前节点的左子树节点都比它小,右子树的节点都比它大。依据这个规律,递归即可解:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode(int x) { val = x; }
 * }
 */

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) return root;

        //当前值大于p和q 那么公共祖先肯定在当前节点的左子树
        if (root.val > p.val && root.val > q.val) {
            //left
            TreeNode left = lowestCommonAncestor(root.left, p, q);
            if (left != null) return left;

        }
        //当前值小于p和q 那么公共祖先肯定在当前节点的右子树
        if (root.val < p.val && root.val < q.val) {
            //right
            TreeNode right = lowestCommonAncestor(root.right, p, q);
            if (right != null) return right;
        }
        //p和q一个在大于root一个小于root,就是一个在在root左 一个在root右边。那么root就是他们公共祖先
        return root;

    }
}

236. 二叉树的最近公共祖先

难度中等947

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:

img

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3

示例 2:

img

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。

示例 3:

输入:root = [1,2], p = 1, q = 2
输出:1

提示:

  • 树中节点数目在范围 [2, 105] 内。
  • -109 <= Node.val <= 109
  • 所有 Node.val 互不相同
  • p != q
  • pq 均存在于给定的二叉树中。

思路

上面这题,要找到公共祖先,需要从下往上找。首选想到的肯定是深度优先,由于是找公共祖先,根节点肯定是最后遍历到,所以这时候用 后序遍历更合适

image-20210221183006795
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //递归的退出条件,找到了p或者找到了q,或者没有节点时返回。
        if(root==p||root==q||root==null) return root;

        //后序
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        //如果 p、q一个在左 一个在右,那么root肯定就是他们最近的公共祖先
        if(right!=null&&left!=null) return root;
        
        return (left!=null)? left:right;
    }
}

1257. 最小公共区域

难度中等

给你一些区域列表 regions ,每个列表的第一个区域都包含这个列表内所有其他区域。

很自然地,如果区域 X 包含区域 Y ,那么区域 X 比区域 Y 大。

给定两个区域 region1region2 ,找到同时包含这两个区域的 最小 区域。

如果区域列表中 r1 包含 r2r3 ,那么数据保证 r2 不会包含 r3

数据同样保证最小公共区域一定存在。

示例 1:

输入:
regions = [["Earth","North America","South America"],
["North America","United States","Canada"],
["United States","New York","Boston"],
["Canada","Ontario","Quebec"],
["South America","Brazil"]],
region1 = "Quebec",
region2 = "New York"
输出:"North America"

提示:

  • 2 <= regions.length <= 10^4
  • region1 != region2
  • 所有字符串只包含英文字母和空格,且最多只有 20 个字母

思路

本题本质也是找 最近公共祖先,不过首先得将数组关系转换成二叉树关系:

class Solution {
    private RegionTreeNode root;
    private Map<String,RegionTreeNode> m_cache = new HashMap<>();

    /**
     * 区域树节点类
     */
    private static class RegionTreeNode {
        String key;// 关键字
        RegionTreeNode parent;// 父节点

        RegionTreeNode(String key) {
            this(key,null);
        }

        RegionTreeNode(String key, RegionTreeNode parent) {
            this.key = key;
            this.parent = parent;
        }
    }
  
   /**
     * 构建树结构
     * @param regions
     */
    private void buildRegionTree(List<List<String>> regions){
        for (List<String> list : regions){
            // 获取父节点的key
            String parentKey = list.get(0);
            if (root == null) {
                root = new RegionTreeNode(parentKey);
                m_cache.put(parentKey,root);
            }
            // 第一个节点是父节点
            RegionTreeNode parentNode = m_cache.get(parentKey);
            for (int i = 1;i<list.size();i++){
                String key = list.get(i);
                RegionTreeNode node = new RegionTreeNode(key);
                m_cache.put(key,node);// 加入缓存
                node.parent = parentNode;
            }
        }
    }

    public String findSmallestRegion(List<List<String>> regions, String region1, String region2) {
        buildRegionTree(regions);
        RegionTreeNode node1 = m_cache.get(region1);
        RegionTreeNode node2 = m_cache.get(region2);
        Set<String> set = new HashSet<>();
        while (node1 != null){
            set.add(node1.key);
            node1 = node1.parent;
        }
        while (node2 != null){
            if (set.contains(node2.key)){
                return node2.key;
            }
            node2 = node2.parent;
        }
        return null;
    }

   
}