【LeetCode热题100】打卡第28天:不同的二叉搜索树验证二叉搜索树对称二叉树

news2024/11/22 6:50:49

文章目录

  • 【LeetCode热题100】打卡第28天:不同的二叉搜索树&验证二叉搜索树&对称二叉树
    • ⛅前言
  • 不同的二叉搜索树
    • 🔒题目
    • 🔑题解
  • 验证二叉搜索树
    • 🔒题目
    • 🔑题解
  • 对称二叉树
    • 🔒题目
    • 🔑题解

【LeetCode热题100】打卡第28天:不同的二叉搜索树&验证二叉搜索树&对称二叉树

⛅前言

大家好,我是知识汲取者,欢迎来到我的LeetCode热题100刷题专栏!

精选 100 道力扣(LeetCode)上最热门的题目,适合初识算法与数据结构的新手和想要在短时间内高效提升的人,熟练掌握这 100 道题,你就已经具备了在代码世界通行的基本能力。在此专栏中,我们将会涵盖各种类型的算法题目,包括但不限于数组、链表、树、字典树、图、排序、搜索、动态规划等等,并会提供详细的解题思路以及Java代码实现。如果你也想刷题,不断提升自己,就请加入我们吧!QQ群号:827302436。我们共同监督打卡,一起学习,一起进步。

LeetCode热题100专栏🚀:LeetCode热题100

Gitee地址📁:知识汲取者 (aghp) - Gitee.com

题目来源📢:LeetCode 热题 100 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台

PS:作者水平有限,如有错误或描述不当的地方,恳请及时告诉作者,作者将不胜感激

不同的二叉搜索树

🔒题目

原题链接:96.不同的二叉搜索树

image-20230625154308713

🔑题解

  • 解法一:递归求解

    这题如果直接根据二叉搜索树的特性去暴力枚举,是十分复杂的,并且耗时也十分高,所以这里我们需要明确二叉搜索树的一个特点,也就是: G ( n ) = ∑ i = 1 n G ( i − 1 ) ∗ G ( n − i ) G(n)=\sum^n_{i=1}G(i-1)*G(n-i) G(n)=i=1nG(i1)G(ni)

    PS:关于这个公式的推导可以参考LeetCode官方题解,或者自行百度,这里不做过多介绍

    通过这个公式我们可以明确,n个节点能够组成多少种二叉搜索树,现在我们只需要使用代码实现上述的公式即可

    /**
     * @author ghp
     * @title
     */
    class Solution {
        public int numTrees(int n) {
            // 递归终止条件(子树只有一个节点或者没有子树,直接返回1)
            if (n == 0 || n == 1) {
                return 1;
            }
            int count = 0;
            // 枚举以节点i为根节点时可以组成二叉树搜索树
            for (int i = 1; i <= n; i++) {
                // 计算左子树有多少种组合
                int l = numTrees(i - 1);
                // 计算右子树有多少种组合
                int r = numTrees(n - i);
                // 更新i个节点时二叉搜索树的组合总数
                count += l * r;
            }
            return count;
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( 2 n ) O(2^n) O(2n),遍历n个节点,并且每个节点都需要递归调用两次
    • 空间复杂度: O ( n ) O(n) O(n),需要递归调用两次,每次递归调用的层数是n,所以递归所需额外开销的空间约为2n

    这里可能你会好奇为什么当前第 i 个节点的二叉搜索树的组合总数 l*r (●’◡’●),因为,左边每一种组合都可以对应右边r种组合,所以这里第 使用节点为 i 为根节点时,总的组合数为 l*r,然后还需要加上前面 计算出来的总和,所以当前 i 个节点,能够组成二叉搜索数的组合数计算公式是 count += l*r

    代码优化:空间换时间

    上面代码存在大量重复计算,左右子树可能节点数量相同,左右子树节点相同时,计算是相同的,所以我们可以使用一个Map集合存储当前节点数量的组合,这样当我们遇到相同节点时,就可以直接使用之前计算的,从而避免了重复计算(这个过程类似于记忆化搜索),同时也能避免很多重复性的递归

    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author ghp
     * @title
     */
    class Solution {
        Map<Integer, Integer> map = new HashMap<>();
    
        public int numTrees(int n) {
            // 递归终止条件(子树只有一个节点或者没有节点,直接返回1)
            if (n == 0 || n == 1) {
                return 1;
            }
            if (map.containsKey(n)) {
                // 当前节点节点,已被计算过了,直接返回之前计算的计算结果
                return map.get(n);
            }
            int count = 0;
            // 枚举以节点i为根节点时可以组成二叉树搜索树
            for (int i = 1; i <= n; i++) {
                // 计算左子树有多少种组合
                int l = numTrees(i - 1);
                // 计算右子树有多少种组合
                int r = numTrees(n - i);
                // 更新二叉搜索树的组合总数
                count += l * r;
            }
            // 将当前节点的计算记过存储到map集合种
            map.put(n, count);
            return count;
        }
    }
    

    优化前:

    image-20230625160704335

    优化后:

    image-20230625160721965

  • 解法二:动态规划

    从前面我们给出的那个公式,我们也可以很明显的发现,这就是一个典型的动态转移方程,所以肯定是可以使用动态规划来实现的

    /**
     * @author ghp
     * @title
     */
    class Solution {
        public int numTrees(int n) {
            int[] g = new int[n + 1];
            g[0] = 1;
            g[1] = 1;
            for (int i = 2; i < g.length; i++) {
                 for (int j = 1; j <= i; j++) {
                    g[i] += g[j - 1] * g[i - j];
                }
            }
            return g[n];
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
    • 空间复杂度: O ( n ) O(n) O(n)

    其中 n n n 为数组中元素的个数

  • 解法三:公式法

    可以直接使用 C n = 2 ( 2 n + 1 ) n + 2 C n − 1 C_n=\frac{2(2n+1)}{n+2}C_{n-1} Cn=n+22(2n+1)Cn1进行计算,满足这个递推式的数称之为卡特兰数

    class Solution {
        public int numTrees(int n) {
            // 提示:我们在这里需要用 long 类型防止计算过程中的溢出
            long count = 1;
            for (int i = 0; i < n; ++i) {
                count = count * 2 * (2 * i + 1) / (i + 2);
            }
            return (int) count;
        }
    }
    

    题解来源:不同的二叉搜索树 - 不同的二叉搜索树 - 力扣(LeetCode)

验证二叉搜索树

🔒题目

原题链接:98.验证二叉搜索树

image-20230625181106720

🔑题解

  • 解法一:递归

    递归的思路比较简单,需要满足左子树是严格递减的右子树是严格递增的,这里需要使用两个变量来记录当前的较小值,也就是说对于左子树要求当前的值比当前最小值还要小,对于右子树要求当前的值比当前最大的值还要大

    注意:这里有一个特别坑的地方,提示中有 − 2 31 < = N o d e . v a l < = 2 31 − 1 -2^{31}<=Node.val<=2^{31}-1 231<=Node.val<=2311,节点的值是可以取到 int 的最大值和最小值,所以这里初始化最大值和最小值,一定不能使用 Integer.MIN_VALUEInteger.MAX_VALUE,这样会导致在进行 if 判断时,可以取等,从而导致 false ,为了避免边界值的情况,我们需要使用 Long.MIN_VALUELong.MAX_VALUE进行初始化

    /**
     * @author ghp
     * @title
     */
    public class Solution {
    
        public boolean isValidBST(TreeNode root) {
            return isBinarySearchTree(root, Long.MIN_VALUE, Long.MAX_VALUE);
        }
    
        private boolean isBinarySearchTree(TreeNode root, long smaller, long bigger) {
            if (root == null) {
                // 到底了,直接返回true
                return true;
            }
            if (root.val <= smaller || root.val >= bigger){
                // 左子树或右子树不符合要求
                return false;
            }
            // 遍历左子树,判断左子树是否严格递减
            boolean f1 = isBinarySearchTree(root.left, smaller, root.val);
            // 遍历右子树,判断右子树是否严格递增
            boolean f2 = isBinarySearchTree(root.right, root.val, bigger);
            // 左子树和右子树都需要满足才是一颗二叉搜索树
            return f1 && f2;
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( n ) O(n) O(n)
    • 空间复杂度: O ( n ) O(n) O(n)

    其中 n n n 为数组中元素的个数

  • 解法二:中序遍历

    这个解法是从二叉搜索树中序遍历的特点出发的,二叉搜索树中序遍历得到的是一个严格递增的数列,我们只需要对二叉树进行中序遍历,并判断中序遍历得到的数列是否是按照严格递增的,即可判断当前这颗二叉树是否是二叉搜索树

    • 递归写法

      public class Solution {
      
          private long pre = Long.MIN_VALUE;
          
          public boolean isValidBST(TreeNode root) {
              return traversal(root);
          }
      
          private boolean traversal(TreeNode root) {
              if (root == null) {
                  return true;
              }
              // 遍历左节点
              boolean f1 = traversal(root.left);
              if (root.val <= pre){
                  // 当前节点的值不符合严格递增
                  return false;
              }
              // 记录上一个节点
              pre = root.val;
              // 遍历右节点
              boolean f2 = traversal(root.right);
              return f1 && f2;
          }
      }
      

      复杂度分析:

      • 时间复杂度: O ( n ) O(n) O(n)
      • 空间复杂度: O ( n ) O(n) O(n)

      其中 n n n 为数组中元素的个数

    • 迭代写法

      public class Solution {
          public boolean isValidBST(TreeNode root) {
              Deque<TreeNode> stack = new ArrayDeque<>();
              TreeNode cur = root;
              long pre = Long.MIN_VALUE;
              boolean f = true;
              while (!stack.isEmpty() || cur != null) {
                  while (cur != null) {
                      stack.push(cur);
                      cur = cur.left;
                  }
                  TreeNode node = stack.pop();
                  if (node.val <= pre) {
                      return false;
                  }
                  pre = node.val;
                  cur = node.right;
              }
              return f;
          }
      }
      

      PS:如果对上面这个迭代写法不是很懂的话,可以参考博主的这篇文章:【LeetCode热题100】打卡第27天:二叉树的遍历

对称二叉树

🔒题目

原题链接:101.对称二叉树

image-20230625220355275

🔑题解

最开始想使用中序遍历来解决,但是失败了,对于[5,4,1,null,1,null,4,2,null,2,null]无法通过!

class Solution {
    public boolean isSymmetric(TreeNode root) {
        Deque<TreeNode> stack = new ArrayDeque<>();
        TreeNode cur = root;
        StringBuilder sb = new StringBuilder();
        while (!stack.isEmpty() || cur != null) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode node = stack.pop();
            if (node.right != null && node.left == null) {
                sb.append(0);
            }
            sb.append(node.val);
            cur = node.right;
            if (node.left != null && node.right == null) {
                sb.append(0);
            }
        }
        String str1 = new String(sb);
        System.out.println(str1);
        String str2 = new String(new StringBuilder(sb).reverse());
        return str1.equals(str2);
    }
}
  • 解法一:递归

    也就是左右同时遍历,这里需要使用两个类似于指针的引用,left和right,left遍历根节点的左子树,right遍历根节点的右子树。最后通过层层的判断,我们可以判断整个二叉树是否对称

    PS:可能是题目写的有点少,一开始我还想到,但是一看题解就恍然大悟w(゚Д゚)w

    image-20230625235638678

    class Solution {
        public boolean isSymmetric(TreeNode root) {
            return check(root, root);
        }
    
        /**
         * 判断二叉树是否对称
         *
         * @param left  左子树
         * @param right 右子树
         * @return
         */
        private boolean check(TreeNode left, TreeNode right) {
            if (left == null && right == null) {
                // 两者都为空,说明对称
                return true;
            }
            if (left == null || right == null) {
                // 一方为空,说明不对成
                return false;
            }
            // 单独判断根节点左右是否对称(除了第一遍,后面和f1是等价的)
            if (left.val != right.val) {
                return false;
            }
            // 判断外侧节点的值是否相同
            boolean f1 = check(left.left, right.right);
            // 判断右侧节点的值是否相同
            boolean f2 = check(left.right, right.left);
            return f1 && f2;
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( n ) O(n) O(n)
    • 空间复杂度: O ( n ) O(n) O(n)

    其中 n n n 为数组中元素的个数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/703608.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

真人手办店值得投资吗?

最近一两年有一种专为普通人设计的真人手办已经开始风靡&#xff0c;很多城市都有了真人手办店的身影&#xff0c;不少人的家里也摆上了属于自己的真人手办&#xff0c;这些手办可以说就是缩小版的我们自己。 除了消费者对这个新生事物有兴趣&#xff0c;许多想要创业的年轻人也…

Prophet算法框架趋势模型、季节模型原理详解与应用实践

本文是在ChatGPT协助下完成&#xff0c;提高了写作速度和效率。 1. 趋势模型 1.1. 趋势模型概述 当我们谈论Prophet中的趋势模型时&#xff0c;我们可以将其理解为描述时间序列数据中整体趋势的一种方式。趋势模型可以告诉我们数据随着时间的推移是如何变化的&#xff0c;是…

Kubernetes Pod篇

Pod基础概念&#xff1a; Pod是kubernetes中最小的资源管理组件&#xff0c;Pod也是最小化运行容器化应用的资源对象。一个Pod代表着集群中运行的一个进程。kubernetes中其他大多数组件都是围绕着Pod来进行支撑和扩展Pod功能的&#xff0c;例如&#xff0c;用于管理Pod运行的S…

Linux--通配结构:*

功能&#xff1a; 与字符匹配后&#xff0c;作用时的作用对象是与字符匹配的文件 示例&#xff1a; ①显示以.c结尾的文件 ②显示file开头的文件③强制&#xff08;f&#xff09;递归&#xff08;r&#xff09;删除以.c结尾的文件 ④删除该目录下的一切文件及目录

联想小新Pro16怎么重装Win10系统?联想小新Pro16重装系统Win10教程

如果您是联想小新Pro16的用户&#xff0c;并且想要重装Win10系统&#xff0c;那么您来对地方了&#xff0c;无论是出于系统崩溃、性能下降还是个人需求改变的原因&#xff0c;重新安装操作系统可以帮助您恢复电脑的正常运行和提升使用体验。以下小编将为您分享联想小新Pro16重装…

Linux内核中常用的C语言技巧详解

概要 Linux内核采用的是GCC编译器&#xff0c;GCC编译器除了支持ANSI C&#xff0c;还支持GNU C。在Linux内核中&#xff0c;许多地方都使用了GNU C语言的扩展特性&#xff0c;如typeof、__attribute__、__aligned、__builtin_等&#xff0c;这些都是GNU C语言的特性。 typeof…

python中,数组 nums[:] 和nums有何区别?

leetcode中有一道题。题目为&#xff1a; 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改…

对于超过最大宽度的文字 想要换行怎么解决

对于超过最大宽度的文字,你可以 1、使用CSS中的word-break属性来换行: .rich-text {word-break: break-all; } break-all 属性允许在字符串内的任何地方换行。 2、使用overflow-wrap属性:break-word 也允许在字符串的任何地方换行。 .rich-text {overflow-wrap: break-wo…

Blazor 自定义可重用基础组件之 InputString(InputText)

为避免命名冲突&#xff0c;将InputText改名为InputString&#xff0c;其实内部类型就是string。 <div class"d-flex"><label class"control-label w-auto mt-2"><b>Lable</b></label><div style"width:InputWidth…

数智化时代,国企人力资源创新突破与高质量发展

二十大以来&#xff0c;在全面建设社会主义现代化背景下&#xff0c;以更大的力度、更切实的措施推动国资国企改革&#xff0c;推动世界一流企业由“培育”转入“加快建设”阶段。历经三年行动&#xff0c;国有企业改革已经取得了令人瞩目的成就。 今年以来&#xff0c;国务院国…

JAVA麻将胡牌算法深度解析

目录 麻将的基本概念 麻将牌的构成 麻将的碰&#xff0c;杠&#xff0c;吃&#xff0c;听&#xff0c;胡 麻将胡牌条件 胡牌算法简介 选将拆分法 算法数据结构 构建数据结构 数据结构使用 牌花色的获取 获取某一花色的牌值 获取某一张牌相邻牌 算法代码实现 基础代…

word另存为pdf失败的原因及处理方法

我们知道&#xff0c;Word可以通过另存为方式直接保存为PDF&#xff0c;其原理其实跟打印机打印差不多&#xff0c;PDF就是一台虚拟的打印机&#xff0c;但有些同学反映word另存为pdf失败&#xff0c;可能的原因是什么呢&#xff1f;又该如何处理呢&#xff1f; word另存为pdf…

【Java|多线程与高并发】CAS以及ABA问题

文章目录 1. 什么是CAS2. ABA问题3. ABA问题的解决 1. 什么是CAS CAS&#xff08;Compare and Swap,“比较和交换”&#xff09;是一种并发编程中常用的原子操作&#xff0c;用于解决多线程环境下的数据竞争和并发访问问题。 CAS操作包含三个操作数&#xff1a;内存位置&#x…

【Git】 Git初相识

文章目录 1. Git的必要性1.1 提出问题1.2 解决问题1.3 注意事项 2. Git的安装2.1 kali下的安装2.3 Windows下的安装 3. Git的配置 1. Git的必要性 1.1 提出问题 在我们的学习或者工作中&#xff0c;经常会遇到一些比较难搞的上司或者老师&#xff0c;让我们交上去的文档改了又…

英伟达开发板学习系列---通过主机名称进行远程登录

1.前言 我们使用Jetson nx 的时候经常需要使用远程工具&#xff0c;而默认的网络配置IP地址采用动态分配&#xff0c;会造成IP地址不确定的问题&#xff0c;故我们可以设置hostname 唯一名字&#xff0c;通过hostname 进行远程连接。 2.通过主机名称进行登录 1.修改主机名称…

使用docker搭建mysql集群

一、技术架构 1、架构图 2、解说 mysql_1、mysql_2、mysql_3是一组主从模式,同理mysql_4、mysql_5、mysql_6也是一组主从模式从上面的图可以看出mysql_1和mysql_4是主节点,可以进行增删改查操作,但是子几点只能查询操作如果mysql_1节点出现问题了&#xff0c;有mysql_4节点组…

Python:使用prometheus-client提交数据到实现prometheus+ grafana数据监控

相关资料 prometheus文档&#xff1a;https://prometheus.io/grafana文档&#xff1a;https://grafana.com/grafana github: https://github.com/grafana/grafanaPyhton客户端https://pypi.org/project/prometheus-client/ 目录 1、使用Python提供数据源2、启动 prometheus3、…

eDNA暴露人类活动轨迹!你的DNA信息可能随时随地在泄露!

环境DNA&#xff08;environmental DNA, eDNA&#xff09;领域发展迅速&#xff0c;但人类eDNA的应用仍未得到充分利用和重视。eDNA分析的广泛应用将为病原体监测、生物多样性监测、濒危和入侵物种检测以及群体遗传学带来许多公认的好处。 佛罗里达大学野生动物疾病基因组学教…

【pytorch函数笔记】torch.sum()、torch.unsqueeze()

1.torch.sum torch.sum(imgs, dim0) # 按列求和 torch.sum(imgs, dim1) # 按行求和 imgs torch.Tensor([i for i in zip( range(10), range(10))]) print(imgs) s1torch.sum(imgs, dim0) # 按列求和 s2torch.sum(imgs, dim1) # 按行求和 print(s1) print(s2) 2.torch.uns…

在前端开发中使用 Python

推荐给需要鼓捣前端应用又不熟悉 JS 的 Pythoner 简介 在使用 Python 进行数据分析的时候&#xff0c;经常需要创建一些动态、交互式的可视化作品。一般会用到如 Plotly、Pyecharts、Bokeh 等库&#xff0c;这些库都是基于前端技术创建可视化作品。所以在自定义可视化的时候&a…