LeetCode题解 二叉树(七):222 完全二叉树的节点个数;110 平衡二叉树;257 二叉树的所有路径

news2024/11/16 1:28:18

前言

阳过之后,已经有一周多没有接触过一道题目了

从今日开始恢复每日一小时的刷题日常

二叉树

222 完全二叉树的节点个数 medium

无论是深度遍历(前中后都好)还是层序遍历,都可以用于求解这道题,只需要使用一个额外的变量记录访问到的结点数量就行。

这道题的考察点在于,如何利用完全二叉树的优势来求解这道题:

作为回归的第一道题目,我们都用一遍

如果是一棵普通的二叉树,那么递归法代码如下:

int getNum(TreeNode* cur) {
    if (!cur) return 0;
    int leftNum = getNum(cur->left);
    int rightNum = getNum(cur->right);

    return 1 + leftNum + rightNum; 
}

int countNodes(TreeNode* root) {
    if (!root) return 0;
    return getNum(root);
}

精简后代码如下:

int countNodes(TreeNode* root) {
    if (root == NULL) return 0;
    return 1 + countNodes(root->left) + countNodes(root->right);
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(log n),算上了递归系统栈占用的空间

而层序遍历代码如下:

int countNodes(TreeNode* root) {
    queue<TreeNode*> que;
    if (root) que.push(root);
    int res = 0;
    while (!que.empty()) {
        int size = que.size();
        for (int i = 0; i < size; i++) {
            TreeNode *cur = que.front();
            que.pop();
            res++;
            if (cur->left) que.push(cur->left);
            if (cur->right) que.push(cur->right);
        }
    }
    return res;
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

接下来我们应当利用一下完全二叉树的特性来求解这道题

实际上,完全二叉树有两种,一种是满二叉树,另一种则是最后一层叶子结点并没有满

那么如何判断完全二叉树满不满,就成了求解这道问题的关键。

有一种方式,就是某结点向左递归的深度始终等于向右递归的深度,就可以断定该二叉树满

利用这个想法,代码如下:

int countNodes(TreeNode* root) {
    if (root == nullptr) return 0;
    TreeNode* left = root->left;
    TreeNode* right = root->right;
    // 这里初始为0是有目的的,为了下面求指数方便
    int leftDepth = 0, rightDepth = 0; 
    while (left) {  // 求左子树深度
        left = left->left;
        leftDepth++;
    }
    while (right) { // 求右子树深度
        right = right->right;
        rightDepth++;
    }
    if (leftDepth == rightDepth) {
        return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0
    }
    return countNodes(root->left) + countNodes(root->right) + 1;
}
  • 时间复杂度:O(log n × log n)
  • 空间复杂度:O(log n)

110 平衡二叉树 easy

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。保持平衡的原因,也是为了在利用二叉树存储数据时,左右两边侧重不至于过于明显。

image-20221231140016537

此处需要再强调一遍,求二叉树的深度,需要前序遍历(中左右),因为要从上而下的去查;

而求解深度,需要后序遍历(左右中),因为要从下而上的查。

递归法,需要递归的是左右子树的高度,最后要进行是否平衡的判断,代码如下:

int getHeight(TreeNode* node) {
    if (node == NULL) {
        return 0;
    }
    int leftHeight = getHeight(node->left);
    if (leftHeight == -1) return -1;
    int rightHeight = getHeight(node->right);
    if (rightHeight == -1) return -1;
    return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
}
bool isBalanced(TreeNode* root) {
    return getHeight(root) == -1 ? false : true;
}

迭代法代码如下(参考代码随想录):

private:
    int getDepth(TreeNode* cur) {
        stack<TreeNode*> st;
        if (cur != NULL) st.push(cur);
        int depth = 0; // 记录深度
        int result = 0;
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                st.push(node);                          // 中
                st.push(NULL);
                depth++;
                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左

            } else {
                st.pop();
                node = st.top();
                st.pop();
                depth--;
            }
            result = result > depth ? result : depth;
        }
        return result;
    }

public:
    bool isBalanced(TreeNode* root) {
        stack<TreeNode*> st;
        if (root == NULL) return true;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();	// 中
            st.pop();
            if (abs(getDepth(node->left) - getDepth(node->right)) > 1) {
                return false;
            }
            if (node->right) st.push(node->right);	// 右(空节点不入栈)
            if (node->left) st.push(node->left);	// 左(空节点不入栈)
        }
        return true;
    }

随想录中给出的说法如下:

当然此题用迭代法,其实效率很低,因为没有很好的模拟回溯的过程,所以迭代法有很多重复的计算。

虽然理论上所有的递归都可以用迭代来实现,但是有的场景难度可能比较大。

例如:都知道回溯法其实就是递归,但是很少人用迭代的方式去实现回溯算法!

因为对于回溯算法已经是非常复杂的递归了,如果再用迭代的话,就是自己给自己找麻烦,效率也并不一定高。

257 二叉树的所有路径 easy

本题初见回溯,但其实并不复杂,回溯的意思就是“倒回去”,回溯是一定要用递归的

递归的传入值,按照本题的要求来,必有的是结点,其次是一个用于存放路径值的数组,另一个则是存放最终结果的string数组;

终止条件也很简单,即遍历到叶子结点就可以结束了;

单层的处理逻辑,就要涉及到回溯的操作,即在遍历时弹出上一个遍历到的结点,此处附上精简后的代码:

void reversal(TreeNode* cur, string path, vector<string>& result) {
    path += to_string(cur->val); // 中
    if (cur->left == NULL && cur->right == NULL) {
        result.push_back(path);
        return;
    }
    if (cur->left) reversal(cur->left, path + "->", result); // 左
    if (cur->right) reversal(cur->right, path + "->", result); // 右
}

vector<string> binaryTreePaths(TreeNode* root) {
    vector<string> result;
    string path;
    if (root == NULL) return result;
    reversal(root, path, result);
    return result;

}

回溯的操作,用在了reversal(cur->left, path + "->", result)中的path + "->",每次函数调用完,实际上path是没有加上"->"的,这就相当于回溯了。

如果要用明白显眼的回溯逻辑,就是如下这样:

if (cur->left) {
    path += "->";
    traversal(cur->left, path, result); // 左
    path.pop_back(); // 回溯 '>'
    path.pop_back(); // 回溯 '-'
}
if (cur->right) {
    path += "->";
    traversal(cur->right, path, result); // 右
    path.pop_back(); // 回溯 '>' 
    path.pop_back(); //  回溯 '-' 
}

本题还有迭代的写法,具体如下:

vector<string> binaryTreePaths(TreeNode* root) {
    stack<TreeNode*> treeSt;// 保存树的遍历节点
    stack<string> pathSt;   // 保存遍历路径的节点
    vector<string> result;  // 保存最终路径集合
    if (root == NULL) return result;
    treeSt.push(root);
    pathSt.push(to_string(root->val));
    while (!treeSt.empty()) {
        TreeNode* node = treeSt.top(); treeSt.pop(); // 取出节点 中
        string path = pathSt.top();pathSt.pop();    // 取出该节点对应的路径
        if (node->left == NULL && node->right == NULL) { // 遇到叶子节点
            result.push_back(path);
        }
        if (node->right) { // 右
            treeSt.push(node->right);
            pathSt.push(path + "->" + to_string(node->right->val));
        }
        if (node->left) { // 左
            treeSt.push(node->left);
            pathSt.push(path + "->" + to_string(node->left->val));
        }
    }
    return result;
}

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

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

相关文章

【电力系统综合能源】“双碳“背景下|综合能源系统中的经济-二氧化碳排放协调最优调度和敏感性分析研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

2022年度回顾|在Jina AI社区连接、分享、共创

在 Jina AI 社区&#xff0c;每天都有来自世界各地的开发者加入&#xff0c;因技术产生连接&#xff0c;因连接带动分享&#xff0c;因分享促进共创。2022 的若干个闪亮的高光时刻&#xff0c;都有来自社区的支持和贡献&#xff1a;在春天&#xff0c;我们发布了 Jina 3.0&…

M1 ARM架构下, linux安装mysql的方式及潜在问题解决

下载: 1. 下载压缩包: 由于m1/m2芯片得使用aarch64架构, 所以普通的x86架构这里就完全无法使用了, 这里推荐在清华镜像网下载对应的aarch64版本: 安装包下载地址 2. Linux系统准备 默认的CentOS是自带mariadb, 和mysql的安装相冲突 , 所以需要提前删除 rpm -qa | grep mari…

Python 三种方法实现截图【详解+完整代码】

人生苦短 我用python 如何用python实现截屏&#xff1f; 一、方法一 PIL中的ImageGrab模块 使用PIL中的ImageGrab模块简单&#xff0c;但是效率有点低 PIL是Python Imaging Library&#xff0c; 它为python解释器提供图像编辑函数能力。 ImageGrab模块可用于将屏幕或剪贴板…

《Linux运维总结:Centos7.6部署redis6.2.8 cluster集群》

一、redis cluster集群规划 Centos7.6部署redis6.2.8 cluster集群资源包 环境信息如下&#xff1a; 主机IP操作系统Redis版本CPU架构端口角色192.168.1.191Centos7.66.2.8x86_647001master192.168.1.192Centos7.66.2.8x86_647002master192.168.1.193Centos7.66.2.8x86_647003m…

如何快速打造一个高权重的短视频账号?短视频运营推广日记(2)

之前做的短视频账号流量一直不好&#xff0c;终于狠下心来注销了&#xff0c;准备重新来过 趁现在账号注销期&#xff0c;好好了解一下短视频账号从0打造的内容&#xff0c;我赢的高权重账号打造内容反复阅读了9遍&#xff0c;终于总结出了属于自己的内容。 看过很多人说要养…

51. CPU和GPU

1. 你的GPU电脑 2. 提升CPU利用率的第一个方法 3. 样例分析 如果一个矩阵是按行存储&#xff0c;访问一行会比访问一列要快 CPU一次读取64字节&#xff08;缓存栈&#xff09;CPU会“聪明的”提前读取下一个&#xff08;缓存栈&#xff09; 4. 提升CPU利用率的第二个方法 高端…

字节一面:服务端挂了,客户端的 TCP 连接还在吗?

服务端进程崩溃&#xff0c;客户端会发生什么&#xff1f; TCP 的连接信息是由内核维护的&#xff0c;所以当服务端的进程崩溃后&#xff0c;内核需要回收该进程的所有 TCP 连接资源&#xff0c;于是内核会发送第一次挥手 FIN 报文&#xff0c;后续的挥手过程也都是在内核完成…

Linux 快照 (snapshot) 原理与实践(二) 快照功能实践

文章目录 0. 概要1. 准备演示数据2. 创建 snapshot-origin 目标3. 创建 snapshot 目标4. 验证 COW 操作4.1 第一次写数据4.2 第二次写数据5. 验证 ROW 操作5.1 第一次写数据5.2 第二次写数据6. 创建 snapshot-merge 目标7. 验证 merge 操作8. 后记0. 概要 上一篇《Linux 快照 …

【修改按钮的大小 Objective-C语言】

一、修改按钮的大小 1.还是上篇文章那个例子 点击加号的时候,使上面的图片按钮变大, 点击减号的时候,使上面的图片按钮变小 2.首先,需要给“加”按钮,注册单击事件 怎么办,拖线吧 右键点击这个列表中的“加”按钮,把这个按钮的Touch Up Inside右边的小圆圈,拖到Vi…

《计算机体系结构量化研究方法》附录B.1 B.2 缓存性能

一、缓存 1、基本知识 &#xff08;1&#xff09;缓存是指可以进行高速数据交换的存储器&#xff0c;它先于内存与CPU交换数据&#xff0c;因此速率很快。&#xff08;from百度&#xff09; &#xff08;2&#xff09;如果处理器在缓存中找到了所需求的数据项&#xff0c;那么…

java 瑞吉外卖优化day1 缓存短信验证 git分支开发 缓存套餐数据 SpringCache

缓存优化 我们将之前写的瑞吉项目push到gitee上&#xff0c;然后新建一个分支v1.0&#xff0c;在v1.0上进行优化&#xff0c;并且push上去 环境搭建 host跟ip都要写自己对应的 &#xff0c;如果没有设置密码 就不用写密码配置 新建RedisConfig配置类 控制不让key序列化&#xf…

springboot入门篇

SpringBoot 文档更新日志 版本更新日期操作描述v1.02021/11/14A基础篇 前言 ​ 很荣幸有机会能以这样的形式和互联网上的各位小伙伴一起学习交流技术课程&#xff0c;这次给大家带来的是Spring家族中比较重要的一门技术课程——SpringBoot。一句话介绍这个技术&#xff0c;应…

Android音乐播放器(高分课设)

实现功能&#xff1a; 1&#xff1a;启动动画&#xff08;运行程序出现一个2秒钟的视频&#xff09;&#xff0c;2秒钟后进入下一界面&#xff01; 2&#xff1a;登录注册&#xff08;账号和密码采用了MD5Utile加密&#xff09;&#xff0c;输入正确的账号和密码进入主界面&a…

新的一年嘚拥有新的壁纸了,python批量采集高清壁纸

前言 大家早好、午好、晚好吖 ❤ ~ 新的一年不得需要新的壁纸&#xff1f;今天我们就来采集一下 环境使用: Python 3.8 解释器 Pycharm 编辑器 第三方模块 import requests >>> pip install requests 如何安装python第三方模块: win R 输入 cmd 点击确定, 输入…

ubuntu 20.04 安装谷歌输入法

目标&#xff1a; 快速安装谷歌输入法 步骤&#xff1a; 安装fcitx-googlepinyin&#xff1a; sudo apt-get install fcitx-googlepinyin 在应用程序里的语言支持中配置language support&#xff1a; 点开语言支持后会提示未完全安装&#xff0c;点击完整安装&#xff1a;…

java线程池理解及底层

并发线程池示例&#xff08;两个示例程序分别用线程 及java自带程池执行一样的程序查看时间&#xff09;&#xff1a; public class ThreadTest {public static void main(String[] args) throws InterruptedException {Long start System.currentTimeMillis();final Random …

一个java短网址转换项目,亲测可用

亲测可用 项目介绍的比较详细,我就不复制粘贴了,直接看项目介绍即可 启动项目后先拿注册账户,登陆,然后创建应用,然后新增短域即可

科研实验室设计基本知识SICOLAB

科研专用实验区 dedicated laboratory area 有特定环境要求&#xff08;如恒温、恒湿、洁净、无菌、防振、防辐射、防电磁干扰等&#xff09;或以精密、大型、特殊实验装置为主&#xff08;如电子显微镜、高精度天平、谱仪等&#xff09;的实验区。 标准单元 standard unit 具…

组内每隔 5 行加一个分隔线

【问题】 I have grouped the data on the column “state” and set the pagebreak option to “Always Excluding First” so that I can see the data related to a particular state in a separate page. I’m trying to add a horizontal line after every 5 rows in th…