草庐IT

代码随想录 | 进阶二叉树

nnn~ 2023-03-28 原文
  • 二叉树的构造默认如下:
/**
 * 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;
 *     }
 * }
 */

654. 最大二叉树

给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:
创建一个根节点,其值为 nums 中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 最大二叉树 。

class Solution {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        TreeNode node = construct(nums, 0, nums.length);//本题数组区间为左闭右开
        return node;
    }

    TreeNode construct(int[] nums,int begin,int end){
        if(nums.length==1){
            return new TreeNode(nums[0]);//数组的长度为1,返回数组中的数构建的结点
        }
        //遍历数组,得到最大值的下标
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        int max = 0;
        for (int i = begin; i < end; i++) {
            map.put(nums[i], i);//数组中的值为key,下标为value
            max = Math.max(max,nums[i]);//找到数组中的最大值
        }
        int index = map.get(max);//数组中最大值的下标
        TreeNode node = new TreeNode(max);//构建结点
        if(index>begin){//如果左区间>=1
            node.left = construct(nums,begin,index);
        }
        if(index<end-1){//如果右区间>=1
            node.right = construct(nums,index+1,end);
        }
        return node;
    }
}
  • 还是构造二叉树的题目,但是比着昨天的两道要简单太多了,感觉自己也慢慢理解二叉树的递归构造



617. 合并二叉树

给你两棵二叉树: root1 和 root2 。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。

ψ(`∇´)ψ 我的思路

  • 前序遍历两棵树的所有结点(思路题目上写的很清楚了)
class Solution {
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        TreeNode node;
        if (root1 == null && root2 == null) {
            return null;
        } else if (root1 == null && root2 != null) {
             node = root2;
        } else if (root2 == null && root1 != null) {
             node = root1;
        } else {
             node = new TreeNode(root1.val+ root2.val);
             node.left = mergeTrees(root1.left,root2.left);
             node.right = mergeTrees(root1.right,root2.right);
        }
        return node;
    }
}
  • 麻麻,我会递归喇?


700. 二叉搜索树中的搜索

给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。

  • 考察BST的特性:当前结点左子树所有结点的值都小于当前结点,当前结点右子树所有结点的值都大于当前结点
class Solution {
    public TreeNode searchBST(TreeNode root, int val) {
       if(root==null){
            return null;
        }
        if(root.val==val){
            return root;
        }
        else if(root.val>val){
            return searchBST(root.left,val);}
        else 
            return searchBST(root.right,val);
    }
}

时间复杂度O(n)

  • 今天的题写的特别顺欸,不知道是我进步了,还是题变简单了



98. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。

BST用中序遍历,结点值递增

class Solution {
    //中序遍历
   long max = Long.MIN_VALUE;//因为题目中最小结点的最小值是从Integer.MIN_VALUE开始的,要找比这个数还小的值,所以选了Long.MIN_VALUE
   public boolean isValidBST(TreeNode root) {
        if(root==null){return true;}//空结点既是二叉搜索树,又是平衡树,又是完全二叉树,又是满二叉树
        boolean left = isValidBST(root.left);//左
        if (root.val>max){//中
            max = root.val;//把当前值赋给max
        } else {
            return false;//当前结点的值小于等于上一个结点的值,不是BST
        }
        boolean right = isValidBST(root.right);//右
        return left && right;
    }
}
  • 代码还可以改进成双指针,这样就不用想着怎么找比结点的最小值还小的数了


530. 二叉搜索树的最小绝对差

ψ(`∇´)ψ 我的思路

  • 中序遍历得到递增的链表,遍历链表求两个相邻元素的差值,取最小
class Solution {
    public int getMinimumDifference(TreeNode root) {
        List<Integer> path = new ArrayList<>();
        inOrder(root,path);
        Integer[] nums = path.toArray(new Integer[0]);
        int fast=1;
        int slow =0;
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < nums.length-1; i++) {
            min = Math.min(min,nums[fast]-nums[slow]);
            fast++;
            slow++;
        }
        return min;
    }

    void inOrder(TreeNode root, List<Integer> path){
        if(root.left!=null){
            inOrder(root.left,path);
        }
        path.add(root.val);
        if(root.right!=null){
            inOrder(root.right,path);
        }
    }
}



501. 二叉搜索树中的众数

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
结点左子树中所含节点的值 小于等于 当前节点的值
结点右子树中所含节点的值 大于等于 当前节点的值
左子树和右子树都是二叉搜索树

本题BST的定义发生改变:
结点左子树中所含节点的值 小于等于 当前节点的值
结点右子树中所含节点的值 大于等于 当前节点的值

  • ❗标记,标记,这题很好,双指针中序遍历BST,通过更新结果集的方式减少一次遍历
  • 下面是我听完视频后的第一次实现,把要用到的东西全加在了参数里,结果不行,参数一直在变。
class Solution {
    TreeNode pre;
    int count;
    public int[] findMode(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        int maxCount = 1;
        inOrder(root,count,maxCount,res);
        int[] result = new int[res.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = res.get(i);
        }
        return result;
    }

    // 中序遍历
    void inOrder(TreeNode cur,int count,int maxCount,List<Integer> res){
        if(cur==null){return;}
        if(cur.left!=null){inOrder(cur.left,count,maxCount,res);}//左
        if(pre == null){
            count = 1;//上一结点为空时,当前结点是第一个结点
        }
        else if (cur.val == pre.val){
            count++;//当前结点和上一结点的值相等,count++
        } else {
            count = 1;//当前结点和上一结点的值不等,count=1
        }
        if(count==maxCount){
            res.add(cur.val);//如果当前结点的次数=最大次数时,当前结点的值加入到结果集中
        }
        if(count>maxCount){
            //如果当前结点的count>最大次数
            maxCount = count;//更新maxCount
            res.clear();//清空res
            res.add(cur.val);
        }
        pre = cur;
        if(cur.right!=null){inOrder(cur.right,count,maxCount,res);}//右
    }
}
  • 把参数拿出来,测试通过
class Solution {
    int count;
    List<Integer> res = new ArrayList<>();
    TreeNode pre;
    int maxCount;
    public int[] findMode(TreeNode root) {
        inOrder(root);
        int[] result = new int[res.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = res.get(i);
        }
        return result;
    }

    // 中序遍历
    void inOrder(TreeNode cur){
        if(cur==null){return;}
        if(cur.left!=null){inOrder(cur.left);}//左
        if(pre == null){
            count = 1;//上一结点为空时,当前结点是第一个结点
        }
        else if (cur.val == pre.val){
            count++;//当前结点和上一结点的值相等,count++
        } else {
            count = 1;
        }
        if(count==maxCount){
            res.add(cur.val);//如果当前结点的次数=最大次数时,当前结点的值加入到结果集中
        }
        if(count>maxCount){
            //如果当前结点的count>最大次数
            maxCount = count;//更新maxCount
            res.clear();//清空res
            res.add(cur.val);
        }
        pre = cur;
        if(cur.right!=null){inOrder(cur.right);}//右
    }
}
  • 挺难的这题,卡我一天



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

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

所有 Node.val 互不相同 。
p != q
p 和 q 均存在于给定的二叉树中。

  • 要找最近的公共祖先,就要从下往上遍历,选择后序遍历。找到目标(p,q)就向上返回,如果当前结点的左右孩子都不空,就是结果,把结果再向上传递。

  • 有一种特例就是,一个目标结点a在另一个目标结点p里面,此时结果为p。这时我们只会找到一个目标结点p,当遇到结点p时就返回p,并不会再遍历到a。因为找不到另一个结点,p会被一直向上传递,最终返回。所以即使我们不再特殊处理这种情况,也对结果没有影响。

/**
 * 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 null;}
        if(root==p||root==q){
            return root;//找到目标值解返回
        }
        TreeNode left = null;
        if(root.left!=null){
            left = lowestCommonAncestor(root.left, p, q);//左
        }
        TreeNode right = null;
        if(root.right!=null){
            right = lowestCommonAncestor(root.right, p, q);//右
        }
        //下面是中的逻辑
        if(left!=null&&right!=null){
            return root;//如果左右都有目标,返回当前结点
        } else if (left!=null&&right==null) {
            return left;//如果左孩子有目标值返回左孩子
        } else if (left==null&&right!=null) {
            return right;//如果右孩子有目标值返回右孩子
        } else {
            return null;//左右都没有找到目标,返回null
        }
    }
}



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

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

  • 用上题的方法完全可解,但是BST有它自己的特性(如果当前结点的值在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 null;}
        if(root.val>p.val&&root.val>q.val){//如果当前结点的值大于p,q的值,就在当前左子树中寻找
            root = lowestCommonAncestor(root.left, p, q);
        } else if(root.val<p.val&&root.val<q.val){//如果当前结点的值小于p,q的值,就在当前右子树中寻找
            root = lowestCommonAncestor(root.right,p,q);
        } else if (root.val>p.val&&root.val<q.val) {//如果当前的结点值在p和q之间,当前结点即为所求
            return root;
        }
        return root;
    }
}



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

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。

  • 思路还是比较清晰的,插入的值小于当前结点的时候,左子树空直接插,左子树不空调用递归插入左子树中。右边一样
class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if(root==null){return new TreeNode(val);}
        if(val< root.val){
            //要插入的值小于当前结点的值
            if(root.left==null){root.left = new TreeNode(val);}//结点的左子树为空,直接插入
            else {root.left = insertIntoBST(root.left,val);}//左子树不空,就在左子树里插入
        }
         else if (val> root.val) {
            //要插入的值大于当前结点的值
            if(root.right==null){root.right = new TreeNode(val);}//结点的右子树为空,直接插入
            else {root.right = insertIntoBST(root.right,val);}//就在右子树里插入
        }
        return root;
    }
}



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

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

分以下5种情况:

  1. 要删除的结点在二叉树中不存在

  2. 要删除的结点存在于二叉树中:
    (1) 要删除的结点下没有孩子,直接返回null
    (2) 要删除的结点下只有左孩子,返回他的左孩子
    (3) 要删除的结点下只有右孩子,返回他的右孩子
    (4) 要删除的结点下有两个孩子,临时变量保存这两个孩子,右孩子继位并找右孩子的最左侧结点,把保存的左孩子挂在上面

class Solution {
    TreeNode temp1;
    TreeNode temp2;
    public TreeNode deleteNode(TreeNode root, int key) {
        if(root==null){return null;}
        if(root.val==key){//找到要删除的结点时
            if(root.left==null&&root.right==null){
                return null;//如果当前结点是叶子结点
            } else if (root.left!=null&&root.right==null) {
                return root.left;//当前结点只有左孩子,左孩子继位
            } else if (root.left==null&&root.right!=null) {
                return root.right;//当前结点只有右孩子,右孩子继位
            } else {
                //当前结点有两个孩子
                temp1 = root.left;//先把root的左孩子存起来
                temp2 = root.right;//把当前结点储存起来
                root = root.right;//让右孩子继位
                while (root.left!=null){root = root.left;}//找到右子树下最左侧的结点
                root.left = temp1;//把原来结点的左孩子挂在找到的结点下
                return temp2;
            }
        }
        if(key<root.val){
            //去左子树下删
            root.left = deleteNode(root.left, key);
        }
        if(key>root.val){
            //去右子树下删
            root.right = deleteNode(root.right, key);
        }
        return root;
    }
}



669. 修剪二叉搜索树

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

class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        if(root==null){
            return null;
        }
        if(root.val<low){
            return trimBST(root.right,low,high);//当前结点的值小于最小值的话,递归当前结点的右子树
        }
        if(root.val>high){
            return trimBST(root.left,low,high);//当前结点的值大于最大值的话,递归当前结点的左子树
        } 
        root.left = trimBST(root.left,low,high);
        root.right = trimBST(root.right,low,high);
        return root;
    }
}
  • 由于本题操作树是二叉搜索树,如果当前结点的值小于最小值的话,结点的左子树一定都小于最小值;右子树中可能含有符合条件的数,所以要再遍历右子树,把遍历好的右子树返回。
  • 当前结点的值大于最大值的情况同上。
  • 如果当前结点满足条件,就改变当前结点的指针。



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

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

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        //递归区间为左闭右开
        TreeNode root = sortedArrayToBST(nums,0, nums.length);
        return root;
    }
     TreeNode sortedArrayToBST(int[] nums,int left,int right) {
        if(left >= right){
            return null;//区间里没有值
        }if(right-left == 1){
            return new TreeNode(nums[left]);//区间里只有一个值
        } 
        int mid = (right + left) / 2; //数组下标中间值
        TreeNode node = new TreeNode(nums[mid]);//新建结点
        node.left = sortedArrayToBST(nums,left,mid);//在中点的左区间里递归
        node.right = sortedArrayToBST(nums, mid+1, right);//在中点的右区间里递归
        return node;
    }
}



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

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

class Solution {
    int sum;
    public TreeNode convertBST(TreeNode root) {
       sum = 0;
       convert(root);//改变树中结点的值
       return root;
    }
    TreeNode convert(TreeNode root) {
        if(root==null){
            return null;
        }
       // 反中序遍历:右中左
       convert(root.right);
       sum += root.val;
       root.val = sum;
       convert(root.left);
       return root;
    }
}

有关代码随想录 | 进阶二叉树的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  3. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  4. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  5. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  6. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  7. 程序员如何提高代码能力? - 2

    前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源

  8. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

  9. git使用常见问题(提交代码,合并冲突) - 2

    文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g

  10. ruby - 这两段代码有什么区别? - 2

    打印1:defsum(i)i=i+[2]end$x=[1]sum($x)print$x打印12:defsum(i)i.push(2)end$x=[1]sum($x)print$x后者是修改全局变量$x。为什么它在第二个例子中被修改而不是在第一个例子中?类Array的任何方法(不仅是push)都会发生这种情况吗? 最佳答案 变量范围在这里无关紧要。在第一段代码中,您仅使用赋值运算符=为变量i赋值,而在第二段代码中,您正在修改$x(也称为i)使用破坏性方法push。赋值从不修改任何对象。它只是提供一个名称来引用一个对象。方法要么是破坏性

随机推荐