24暑假算法刷题 | Day15 | LeetCode 110. 平衡二叉树,257. 二叉树的所有路径,404. 左叶子之和,222. 完全二叉树的节点个数

news2024/11/13 9:12:38

目录

  • 110. 平衡二叉树
    • 题目描述
    • 题解
  • 257. 二叉树的所有路径
    • 题目描述
    • 题解
  • 404. 左叶子之和
    • 题目描述
    • 题解
  • 222. 完全二叉树的节点个数
    • 题目描述
    • 题解


110. 平衡二叉树

点此跳转题目链接

题目描述

给定一个二叉树,判断它是否是平衡二叉树

平衡二叉树 是指该树所有节点的左右子树的深度相差不超过 1。

示例 1:

img

输入:root = [3,9,20,null,null,15,7]
输出:true

示例 2:

img

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

示例 3:

输入:root = []
输出:true

提示:

  • 树中的节点数在范围 [0, 5000]
  • -104 <= Node.val <= 104

题解

显然,一个二叉树是平衡的,当且仅当它的所有子树都是平衡的。听起来就很适合递归秒了:

int getDepth(TreeNode *root)
{
    if (!root)
        return 0;
    return max(getDepth(root->left), getDepth(root->right)) + 1;
}

bool isBalanced(TreeNode *root)
{
    if (!root)
        return true; // 空树是平衡树
    return abs(getDepth(root->left) - getDepth(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
}

相应地,考虑一下迭代法。由于需要判断各节点“左右子树的深度差”是否大于1,联想到基于后序遍历实现本题:因为后序遍历处理某一节点时,总是已经访问过其左右子树节点了。

// 基于层序遍历获取某个节点的深度
int getDepth(TreeNode *root)
{
    if (!root)
        return 0;
    queue<TreeNode *> q;
    q.push(root);
    int depth = 0;
    while (!q.empty())
    {
        int size = q.size();
        for (int i = 0; i < size; i++)
        {
            if (q.front()->left)
                q.push(q.front()->left);
            if (q.front()->right)
                q.push(q.front()->right);
            q.pop();
        }
        depth++;
    }
    return depth;
}

// 基于后序遍历检验平衡树
bool isBalanced(TreeNode *root)
{
    if (!root)
        return true;
    // 统一迭代法的后序遍历
    stack<TreeNode *> st;
    st.push(root);
    while (!st.empty())
    {
        TreeNode *cur = st.top();
        st.pop();
        if (cur)
        {
            st.push(cur);     // 中
            st.push(nullptr); // 空节点标记
            if (cur->left)
                st.push(cur->left); // 左
            if (cur->right)
                st.push(cur->right); // 右
        }
        else
        {
            if (abs(getDepth_II(st.top()->left) - getDepth_II(st.top()->right)) > 1)
                return false;
            st.pop();
        }
    }
    return true;
}

257. 二叉树的所有路径

点此跳转题目链接

题目描述

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

叶子节点 是指没有子节点的节点。

示例 1:

在这里插入图片描述

输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]

示例 2:

输入:root = [1]
输出:["1"]

提示:

  • 树中节点的数目在范围 [1, 100]
  • -100 <= Node.val <= 100

题解

首先考虑递归解法,有两种,一是传统的DFS(深度优先搜索):

void getPathDFS(TreeNode *root, string path, vector<string> &res)
{
    if (root)
    {
        path += to_string(root->val);
        // 递归出口:遍历到叶子节点
        if (!root->left && !root->right)
            res.push_back(path);
        else
        {
            path += "->";
            getPathDFS(root->left, path, res);
            getPathDFS(root->right, path, res);
        }
    }
}

vector<string> binaryTreePaths(TreeNode *root)
{
    vector<string> res;
    getPathDFS(root, "", res);
    return res;
}

二是结合回溯法,在基于递归的前序遍历框架下实现:

void traversal(TreeNode *root, vector<int> &paths, vector<string> &res)
{
    // 由于需要找到所有路径,采用前序遍历实现
    paths.push_back(root->val); // 中
    // 递归出口:遍历到叶子节点
    if (!root->left && !root->right)
    {
        string path = to_string(paths[0]);
        for (int i = 1; i < paths.size(); ++i)
            path += "->" + to_string(paths[i]);
        res.push_back(path);
        return;
    }
    if (root->left)
    {
        traversal(root->left, paths, res); // 左
        paths.pop_back();                  // 回溯
    }
    if (root->right)
    {
        traversal(root->right, paths, res); // 右
        paths.pop_back();                   // 回溯
    }
}

vector<string> binaryTreePaths(TreeNode *root)
{
    vector<string> res; // 最终的结果集
    vector<int> paths;  // 存储每条路径的数组(按照路径上节点的值)
    if (!root)
        return res;
    traversal(root, paths, res);
    return res;
}

其中每次回溯的作用相当于回退到上一个“分支点”,再选择一条与之前不同的分支进行操作,原理可以参考 代码随想录本题讲解 中的这张图,从始至终走一遍应该就能领会了:

在这里插入图片描述

最后还是考虑一下迭代法,同上面一样,要基于前序遍历的框架实现,具体来说就是在 统一迭代法 的基础上,新建一个存储当前路径的栈,随着“右左中”节点的入栈,相应的路径也要更新、入栈:

vector<string> binaryTreePaths(TreeNode *root)
{
    // 基于前序遍历的统一迭代法实现
    vector<string> res;
    stack<string> pathSt;
    stack<TreeNode *> nodeSt;
    if (!root)
        return res;
    nodeSt.push(root);
    pathSt.push(to_string(root->val));
    while (!nodeSt.empty())
    {
        TreeNode *node = nodeSt.top();
        nodeSt.pop();
        string path = pathSt.top();
        pathSt.pop();
        if (node)
        {
            if (node->right)
            {
                pathSt.push(path + "->" + to_string(node->right->val));
                nodeSt.push(node->right); // 右
            }
            if (node->left)
            {
                pathSt.push(path + "->" + to_string(node->left->val));
                nodeSt.push(node->left); // 左
            }
            nodeSt.push(node);    // 中
            nodeSt.push(nullptr); // 空节点标记
            pathSt.push(path);    // 记录当前路径
        }
        else
        {
            if (!nodeSt.top()->left && !nodeSt.top()->right)
                res.push_back(path); // 已到叶子节点:当前路径加入结果集
            nodeSt.pop();
        }
    }
    return res;
}

⚠️ 值得注意的是,为了保证每次路径栈顶的路径与节点栈顶的节点“一一对应”,两个栈要同步操作(一起 pushpop 。唯一例外的是最后路径加入结果集时,节点栈 pop 了但是路径栈没有:

...
else
{
    if (!nodeSt.top()->left && !nodeSt.top()->right)
        res.push_back(path); // 已到叶子节点:当前路径加入结果集
    nodeSt.pop();
}

这是因为在每次循环一开始,两个栈就已经同时 pop 过了:

while (!nodeSt.empty())
{
    TreeNode *node = nodeSt.top();
    nodeSt.pop();
    string path = pathSt.top();
    pathSt.pop();
    ...

而若要进入 else 、记录结果,上面这里节点栈弹出的是标记用的空节点,所以它自己最后还要再 pop 一次来弹出真正的节点,而路径栈就不用了。


404. 左叶子之和

点此跳转题目链接

题目描述

给定二叉树的根节点 root ,返回所有左叶子之和。

示例 1:

在这里插入图片描述

输入: root = [3,9,20,null,null,15,7] 
输出: 24 
解释: 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24

示例 2:

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

提示:

  • 节点数在 [1, 1000] 范围内
  • -1000 <= Node.val <= 1000

题解

比较简单,还是可以递归和迭代实现。要注意的就是判断左叶子只能通过其父节点判断:

root->left && !root->left->left && !root->left->right

即父节点有左孩子、且这个左孩子无左右孩子,则这个左孩子是左叶子。

递归法

int sumOfLeftLeaves(TreeNode *root)
{
    // 递归出口1:当前节点为空
    if (!root)
        return 0;
    int leftSum = sumOfLeftLeaves(root->left);
    // 递归出口2:当前节点的左孩子是左叶子
    if (root->left && !root->left->left && !root->left->right)
        leftSum = root->left->val;
    return leftSum + sumOfLeftLeaves(root->right);
}

迭代法

int sumOfLeftLeaves_II(TreeNode *root) {
    if (!root)
        return 0;
    queue<TreeNode*> q;
    q.push(root);
    int sum = 0;
    while (!q.empty()) {
        int size = q.size();
        for (int i = 0; i < size; ++i) {
            TreeNode *cur = q.front();
            if (cur->left) {
                q.push(cur->left);
                if (!cur->left->left && !cur->left->right)
                    sum += cur->left->val;
            }
            if (cur->right)
                q.push(cur->right);
            q.pop();
        }
    }
    return sum;
}

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

点此跳转题目链接

题目描述

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。

示例 1:

在这里插入图片描述

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

示例 2:

输入:root = []
输出:0

示例 3:

输入:root = [1]
输出:1

提示:

  • 树中节点的数目范围是[0, 5 * 104]
  • 0 <= Node.val <= 5 * 104
  • 题目数据保证输入的树是 完全二叉树

进阶: 遍历树来统计节点是一种时间复杂度为 O(n) 的简单解决方案。你可以设计一个更快的算法吗?

题解

无脑算法当然就是遍历整棵树,记录节点个数即可(这样应该直接层序遍历最方便),时间复杂度为 O ( n ) O(n) O(n) ,不赘述。

考虑利用完全二叉树的性质提升速度。根据其性质,完全二叉树的子树中有很多都是满二叉树,而一个 n n n 层满二叉树的节点个数为 2 n − 1 2^n - 1 2n1 。所以我们可以利用这点,进行带剪枝的递归遍历:

  • 以当前节点为根,所得子树为满二叉树,则按公式计算节点数
  • 否则,递归计算节点数

其中,判断满二叉树的方法也很简单高效:看“最左”枝和“最右”枝的深度是否相同即可。

代码(C++)

int countNodes(TreeNode *root)
{
    if (!root)
        return 0;
    TreeNode *left = root->left;
    TreeNode *right = root->right;
    int leftDepth = 0, rightDepth = 0; // 左、右子树深度
    while (left) {
        left = left->left;
        leftDepth++;
    }
    while (right) {
        right = right->right;
        rightDepth++;
    }
    if (leftDepth == rightDepth) // 满二叉树,直接用公式计算
        return (2 << leftDepth) - 1;
    return countNodes(root->left) + countNodes(root->right) + 1;
}

这里用位运算, 2 << leftDepth 相当于 2 l e f t D e p t h + 1 2^{leftDepth + 1} 2leftDepth+1 ,其中+1是因为 leftDepth 是子树深度,还要加上根节点的1才是相应的树深度 n n n

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

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

相关文章

AI算法16-贝叶斯线性回归算法Bayesian Linear Regression | BLR

贝叶斯线性回归算法简介 频率主义线性回归概述 线性回归的频率主义观点可能你已经学过了&#xff1a;该模型假定因变量&#xff08;y&#xff09;是权重乘以一组自变量&#xff08;x&#xff09;的线性组合。完整的公式还包含一个误差项以解释随机采样噪声。如有两个自变量时…

原码、补码、反码、移码是什么?

计算机很多术语翻译成中文之后&#xff0c;不知道是译者出于什么目的&#xff0c;往往将其翻译成一个很难懂的名词。 奇怪的数学定义 下面是关于原码的“吐槽”&#xff0c;可以当作扩展。你可以不看&#xff0c;直接去下一章&#xff0c;没有任何影响。 原码的吐槽放在前面是…

【元器件】二极管、三极管、MOS管

二极管 D 二极管是一种具有两个电极&#xff08;即正极和负极&#xff09;的电子器件。它是一种非线性元件&#xff0c;具有许多重要的功能和应用 三极管 Q 概述 一种控制电流的半导体器件&#xff0c;其作用是把微弱信号放大成幅度值较大的电信号&#xff0c;也用作无触点开…

代理IP服务中的代理池大小有何影响?

在当今数字化时代&#xff0c;网络爬虫已经成为获取各类信息必不可少的工具。在大规模数据抓取中&#xff0c;使用单一 IP 地址或同一 IP 代理往往会面临抓取可靠性降低、地理位置受限、请求次数受限等一系列问题。为了克服这些问题&#xff0c;构建代理池成为一种有效的解决方…

TikTok账号矩阵运营怎么做?

这几年&#xff0c;聊到出海避不过海外抖音&#xff0c;也就是TikTok&#xff0c;聊到TikTok电商直播就离不开账号矩阵&#xff1b; 在TikTok上&#xff0c;矩阵养号已经成为了出海电商人的流行策略&#xff0c;归根结底还是因为矩阵养号可以用最小的力&#xff0c;获得更大的…

沧穹科技助力杭州东站网约车服务全面升级

沧穹科技助力杭州东站完成网约车智能化服务全面革新升级&#xff0c;这一举措显著提升了杭州东站网约车服务的效率与乘客体验。以下是对这一革新升级的详细解析&#xff1a; 一、革新背景 随着网约车行业的快速发展&#xff0c;杭州东站作为华东地区重要的交通枢纽&#xff0c;…

VRRP虚拟路由冗余技术

VRRP虚拟路由冗余技术&#xff1a;是一种路由容错协议&#xff0c;用于在网络中提供路由器的冗余备份。它通过将多个路由器虚拟成一个虚拟路由器并且多个路由器之间共享一个虚拟IP地址来实现冗余和高可用性。当承担转发业务的主路由器出现故障时&#xff0c;其他备份路由器可以…

输出调节求解跟踪问题(二阶线性系统)

本文研究了一种基于增广系统的领导者-跟随者控制框架&#xff0c;旨在实现跟随者系统对领导者参考信号的精确跟踪。首先&#xff0c;建立了跟随者和领导者的独立状态空间方程&#xff0c;分别描述了它们的动态行为和输出关系。随后&#xff0c;通过将两者的状态空间方程结合成增…

AI口语练习APP的技术挑战

实现基于大模型的口语练习系统是一项复杂且具有挑战性的任务&#xff0c;涉及多项技术和工程难点。以下是一些主要的技术难点。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1. 语音识别准确率 口语练习APP需要能够准确识别用户的语…

探索Python自然语言处理的新篇章:jionlp库介绍

探索Python自然语言处理的新篇章&#xff1a;jionlp库介绍 1. 背景&#xff1a;为什么选择jionlp&#xff1f; 在Python的生态中&#xff0c;自然语言处理&#xff08;NLP&#xff09;是一个活跃且不断发展的领域。jionlp是一个专注于中文自然语言处理的库&#xff0c;它提供了…

智慧消防建设方案(完整方案参考PPT)

智慧消防系统建设方案旨在通过物联网、大数据与云计算技术&#xff0c;集成火灾自动报警、智能监控、应急指挥等功能于一体。方案部署智能传感器监测火情&#xff0c;实时数据分析预警&#xff0c;实现火灾早发现、早处置。构建可视化指挥平台&#xff0c;优化应急预案&#xf…

Google资深工程师深度讲解Go语言-课程笔记

课程目录&#xff1a; 第1章 课程介绍 欢迎大家来到深度讲解Go语言的课堂。本课程将从基本语法讲起&#xff0c;逐渐深入&#xff0c;帮助同学深度理解Go语言面向接口&#xff0c;函数式编程&#xff0c;错误处理&#xff0c;测试&#xff0c;并行计算等元素&#xff0c;并带…

高性能内存对象缓存Memcached

memcached常用架构 memcached分布式示例图 一致性hash算法简单示例图

Yum包下载

1. 起因 内网有一台服务器需要升级php版本,维护的同学又不想二进制安装.服务器只有一个光盘的yum仓库 2. 解决方法 解决思路如下: 外网找一台机器配置php8.3.8的仓库外网服务器下载软件集并打包内网服务器上传并解压实现升级 2.1 下载php8.3.8仓库 配置php仓库 rootcent…

SQL 子查询中,查询了一个不存在的字段,居然不报错

前言 前几天在做一个需求&#xff0c;用户所在的部门被删除了&#xff0c;对应用户的角色也要清空。测试测的时候发现&#xff0c;只要测我的这个需求系统的所有角色都被删除了。。。。。。。 我看了日志也没报错呀&#xff0c;我也没有删除所有账号的角色呀。我有点不相信&a…

(一)原生js案例之图片轮播

原生js实现的两种播放效果 效果一 循环播放&#xff0c;单一的效果 代码实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sc…

4款良心软件,免费又实用,内存满了都舍不得卸载

以下几款高质量软件&#xff0c;若是不曾体验&#xff0c;实在是遗憾可惜。 PDF Guru 这是一款开源免费的PDF编辑软件&#xff0c;打开之后功能一目了然。 可以拆分、合并PDF&#xff0c;也可以给PDF添加水印和密码&#xff0c;同时也可以去除别人PDF里的水印或密码&#xff0…

状态管理的艺术:探索Flutter的Provider库

状态管理的艺术&#xff1a;探索Flutter的Provider库 前言 上一篇文章中&#xff0c;我们详细介绍了 Flutter 应用中的状态管理&#xff0c;以及 StatefulWidget 和 setState 的使用。 本篇我们继续介绍另一个实现状态管理的方式&#xff1a;Provider。 Provider优缺点 基…

Spock单元测试框架使用介绍和实践

背景 单元测试是保证我们写的代码是我们想要的结果的最有效的办法。根据下面的数据图统计&#xff0c;单元测试从长期来看也有很大的收益。 单元测试收益: 它是最容易保证代码覆盖率达到100%的测试。可以⼤幅降低上线时的紧张指数。单元测试能更快地发现问题。单元测试的性…

STM32 UART 硬件结构

访问串口与读写内存无差&#xff0c;串口将寄存器中的值通过数据线一位一位的传输出去 协议 设置波特率&#xff0c;数据位 115200 8 n 1 BSRR/CR 查询方式进行数据的发送与接收 &#xff08;在一个while循环中判断状态&#xff0c;然后读取数据&#xff09; 1、发送…