代码随想录第十五天 | Leecode 110. 平衡二叉树、257. 二叉树的所有路径、404. 左叶子之和、222. 完全二叉树的节点个数
Leecode 110. 平衡二叉树
题目描述
给定一个二叉树,判断它是否是 平衡二叉树(是指该树所有节点的左右子树的高度相差不超过 1。)
- 示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:true
- 示例 2:
输入:root = [1,2,2,3,3,null,null,4,4]
输出:false
- 示例 3:
输入:
root = []
输出:true
递归法求高度
首先本题是需要求高度而非深度,二者的区别主要在于:
- 深度是自上往下计数
- 高度是自下往上计数
如果采用深度的方式进行递归,由于递归到某个节点的时候是自上而下进行计数,还未判断过当前节点的之下的节点,自然无法判断其是否为平衡二叉树的根节点。会导致如果漏判子树中出现非平衡二叉树、而左右子树高度都相等的情况,即下面测试用例:
输入:
[1,2,2,3,null,null,3,4,null,null,4]
如果使用深度递归无法判断
另外,如果是采用深度递归,在递归函数的传入参数中,还有一个参数用于表示当前深度,即深度递归函数一般为:int getepth(TreeNode* curNode, int depth)。但如果是高度递归,不需要传入参数,一般形式为:int getHeight(TreeNode* curNode)。在到达空节点的时候返回0,同时在后续递归调用中进行+1操作,这样即可实现自下而上的高度递归。
下面给出本题代码:
class Solution {
public:
int getHeight(TreeNode* curNode){ // 记高度-1表示不是平衡二叉树,此时再计算高度已无意义,故记作-1
if(!curNode) return 0; // 如果当前节点为空,此时返回高度为0
int leftHeight = getHeight(curNode->left); // 获取左子节点的高度
int rightHeight = getHeight(curNode->right); // 获取右子节点的高度
if(leftHeight == -1 || rightHeight == -1) return -1; // 如果两个子节点中有一个的高度为-1,说明已经不是平衡二叉树,返回高度-1
if( abs(leftHeight - rightHeight) > 1) return -1; // 如果两个子节点高度差大于1,说明不是平衡二叉树,则返回高度-1
return max(leftHeight, rightHeight) + 1; // 当前节点的高度 = 左右两个节点中更大的高度 + 1
}
bool isBalanced(TreeNode* root) {
return getHeight(root) != -1; // 只要最后根节点的高度不为 -1, 则为真
}
};
上面代码中,需要注意的一点在于,我们将非平衡二叉树的根节点高度记作-1,原因是此时再记录其高度已经没有意义,使用高度-1并在其中增加一行判断(如果子节点的高度为-1则当前节点高度也为-1),这样可以使得一旦出现非平衡二叉树的节点,就可以一直回传到最终根节点中。
Leecode 257. 二叉树的所有路径
题目描述
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
- 示例 1:
输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]
- 示例 2:
输入:
root = [1]
输出:["1"]
回溯递归
题外话:这题真特么难。。真的是简单题吗,给我做得怀疑人生了。。前几天刷下来感觉自己又行了,今天这一道题又给打回现实了
本题需要输出每一条通往叶节点的路径,考虑采用回溯的方法,遍历走到叶节点之后记录当前路径,然后返回进行回溯。思想上还算能够想通,但是如果没有接触过回溯的话想要实现代码还是有一定难度的(说的就是我)。下面结合代码来进行说明:
class Solution {
public:
void pathHelper(TreeNode* curNode, vector<int>& path ,vector<string>& result){ // 传入参数为:当前节点、从根节点走到当前节点的路径,用于存放结果
path.push_back(curNode->val); // 第一步先将当前节点记录到路径中
if(!curNode->left && !curNode->right){ // 如果当前节点是叶节点,将路径转换为string进行存放
string pathString = to_string(path[0]); // 从这一步开始将vector<int> 中的路径转换为string类型
for(int i = 1; i < path.size(); i++){
pathString += ("->" + to_string(path[i]));
}
result.push_back(pathString); // 将上面路径转换成的字符串存入result中
}
if(curNode->left) { // 如果当前节点不是叶节点,则需要往能走的方法继续走
pathHelper(curNode->left, path, result); // 往左子节点走下去,递归地去左子树中寻找并记录路径
path.pop_back(); // 由于使用上面递归之后,会在递归函数中传入curNode->left->val,现在递归结束回退后需要将其删除
}
if(curNode->right) { // 如果有右子节点,需要去记录右子树中的路径
pathHelper(curNode->right, path, result); // 递归右子树寻找路径
path.pop_back(); // 同样也要将右子节点给弹出
}
return;
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result; // 建立用于存放结果的vector
if(!root) return result; // 如果根节点为空,则直接返回空的vector
vector<int> path; //
pathHelper(root, path, result);
return result;
}
};
上面代码中的path.pop_back();体现了回溯的过程,但同样可以使用值传递来代替引用传递,这样可以免去这一步pop回溯。同时可以直接使用字符串来存放路径,遍历到叶节点后直接push记录即可,而不必再使用一个存放int的vector容器。故可以将代码简化如下:
class Solution {
public:
void pathHelper(TreeNode* curNode, string path, vector<string>& result){
if(curNode) path += (to_string(curNode->val) + "->");
if(!curNode->left && !curNode->right){
path.resize(path.size()-2); // 除去最后的箭头符号
result.push_back(path);
}
if(curNode->left) pathHelper(curNode->left, path, result); // path使用值传递,因此不需要回退
if(curNode->right) pathHelper(curNode->right, path, result);
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
if(!root) return result;
string path = ""; // 初始化path
pathHelper(root, path, result);
return result;
}
};
经过本题的历练,希望之后能够掌握回溯的思想。
Leecode 404. 左叶子之和
题目描述
给定二叉树的根节点 root ,返回所有左叶子之和。
- 示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:24
解释: 在这个二叉树中,有两个左叶子,分别是9和15,所以返回24
- 示例 2:
输入:
root = [1]
输出:0
递归法
题目要求只对左叶子求和,那么可以考虑使用一个布尔变量,来记录上一步遍历到当前节点的时候是往左还是往右。如果是往左来的则为true,否则为false。那么我们可以得到如下代码:
class Solution {
public:
void sumHelper(TreeNode* curNode, int &curSum, bool isLeft){ // 使用一个布尔变量来记录当前节点是否为其父节点的左子节点
if(!curNode) return; // 如果当前子节点为空,则直接返回
if(!curNode->left && !curNode->right){ // 如果当前节点是叶节点
if(!isLeft) return; // 还需要判断是否是左子节点,如果不是则直接返回
curSum += curNode->val; // 如果是题目要求得左叶节点,则将其中的值加到curSum中
return;
}
sumHelper(curNode->left, curSum, true); // 递归地对左子节点求和,需要注意此时传入的布尔变量,左为true,右为false
sumHelper(curNode->right, curSum, false); // 递归地对右子节点求和
return;
}
int sumOfLeftLeaves(TreeNode* root) {
int sum = 0;
sumHelper(root, sum, false); // 根节点不是左子节点,故一开始传入的布尔变量为false
return sum;
}
};
Leecode 222. 完全二叉树的节点个数
题目描述
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(从第 0 层开始),则该层包含 1~ 2h 个节点。
- 示例 1:
输入:root = [1,2,3,4,5,6]
输出:6
- 示例 2:
输入:
root = []
输出:0
- 示例 3:
输入:
root = [1]
输出:1
解法1,当做普通二叉树递归求解
如果不考虑这是一颗完全二叉树,那么根据:树中节点数 = 左子树节点数 + 右子树节点数 + 1;使用该公式可以直接得到下面递归代码:
class Solution {
public:
int countNodes(TreeNode* root) {
if(root == nullptr) return 0;
return countNodes(root->left) + countNodes(root->right) + 1;
}
};
我们可以分析上面节点的时间复杂度,相当于将每个节点都遍历了一次,故时间复杂度为\(O(n)\)。接下来我们考虑利用题目中给出的当前这棵树是完全二叉树的情况,如何降低时间复杂度。
解法2 完全二叉树的节点个数
对于完全二叉树而言,如果恰好是一颗满二叉树,那么总节点个数为:深度\(d\)的平方-1,即\(2^d - 1\)。但完全二叉树并不都恰好是满二叉树,不过幸好对于每一颗完全二叉树,其子树最后都有一部分是完全二叉树。故我们可以利用这个规律,得到下面代码:
class Solution {
public:
int countNodes(TreeNode* root) {
if(!root) return 0;
TreeNode* leftNode = root->left;
TreeNode* rightNode = root->right;
int leftDep = 0;
int rightDep = 0;
while(leftNode){
leftNode = leftNode->left;
leftDep++;
}
while(rightNode){
rightNode = rightNode->right;
rightDep++;
}
if(leftDep == rightDep) return (2 << leftDep) - 1; // 左移位运算符相当于是求幂次
return countNodes(root->left) + countNodes(root->right) + 1;
}
};
上面代码利用了完全二叉树的特性,尽管看起来代码变得复杂了很多,但实际上时间复杂度比原本更低,变成了\(O(\log n* \log n)\)。在节点数增多的时候,会比原本的时间复杂度\(O(n)\)要低很多。
另外,对于上面代码中用到的左移位运算符<<,相当于是将二进制数整体左移,得到的结果相当于是乘了一个\(2^x\)的2次幂。
今日总结
今天学习了回溯的算法,需要注意回溯时如果使用引用传递则需要手动进行回溯,而如果使用值传递则不必。同时又学习了完全二叉树的特性,并充分利用其特性降低时间复杂度。
同时记录一下当前力扣刷题记录,已经到了第54题:
代码随想录第十五天 | Leecode 110. 平衡二叉树、257. 二叉树的所有路径、404. 左叶子之和、222. 完全二叉树的节点个数的更多相关文章
- 代码随想录算法训练营day17 | leetcode ● 110.平衡二叉树 ● 257. 二叉树的所有路径 ● 404.左叶子之和
LeetCode 110.平衡二叉树 分析1.0 求左子树高度和右子树高度,若高度差>1,则返回false,所以我递归了两遍 class Solution { public boolean is ...
- 代码随想录算法训练营day16 | leetcode ● 104.二叉树的最大深度 559.n叉树的最大深度 ● 111.二叉树的最小深度 ● 222.完全二叉树的节点个数
基础知识 二叉树的多种遍历方式,每种遍历方式各有其特点 LeetCode 104.二叉树的最大深度 分析1.0 往下遍历深度++,往上回溯深度-- class Solution { int deep ...
- x264代码剖析(十五):核心算法之宏块编码中的变换编码
x264代码剖析(十五):核心算法之宏块编码中的变换编码 为了进一步节省图像的传输码率.须要对图像进行压缩,通常採用变换编码及量化来消除图像中的相关性以降低图像编码的动态范围.本文主要介绍变换编码的相 ...
- 代码随想录算法训练营day18 | leetcode 513.找树左下角的值 ● 112. 路径总和 113.路径总和ii ● 106.从中序与后序遍历序列构造二叉树
LeetCode 513.找树左下角的值 分析1.0 二叉树的 最底层 最左边 节点的值,层序遍历获取最后一层首个节点值,记录每一层的首个节点,当没有下一层时,返回这个节点 class Solutio ...
- 代码随想录算法训练营day14 | leetcode 层序遍历 226.翻转二叉树 101.对称二叉树 2
层序遍历 /** * 二叉树的层序遍历 */ class QueueTraverse { /** * 存放一层一层的数据 */ public List<List<Integer>&g ...
- 代码随想录算法训练营day07 | leetcode 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和
LeetCode 454.四数相加II 分析1.0 这个最直接暴力法,不过过于暴力了,害怕.jpg 失误 读题理解失误:题目要求的是四元组的个数,读完题到我这里成了输出四元组,悲哉 分析2.0 记录有 ...
- 【leetcode 简单】 第九十四题 左叶子之和
计算给定二叉树的所有左叶子之和. 示例: 3 / \ 9 20 / \ 15 7 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24 # Definition for a binary ...
- 十五个常用的jquery代码段【转】
好的文章顶一个 回到顶部按钮 通过使用 jQuery 中的 animate 和 scrollTop 方法,你无需插件便可创建一个简单地回到顶部动画: 1 // Back to top 2 $('a.t ...
- 十五个常用的jquery代码段
十五个常用的jquery代码段 回到顶部按钮 通过使用 jQuery 中的 animate 和 scrollTop 方法,你无需插件便可创建一个简单地回到顶部动画: 1 // Back to top ...
- python3.4学习笔记(二十五) Python 调用mysql redis实例代码
python3.4学习笔记(二十五) Python 调用mysql redis实例代码 #coding: utf-8 __author__ = 'zdz8207' #python2.7 import ...
随机推荐
- 使用PhantomJS解决VUE项目无法被百度收录
一.安装PhantomJS 安装文章:https://www.cnblogs.com/robots2/p/17340143.html 二.编写脚本spider.js // spider.js 'use ...
- Luogu P5663 CSP-J2019 加工零件 题解 [ 绿 ] [ 分层图最短路 ]
加工零件:非常好的一道图论题.CCF 普及组的题目大概也只有图论出的比较巧妙了. 题意简述:给你一张无向图,\(q\) 次询问,判断是否存在一条从 \(a\) 到 \(1\) 且长度为 \(L\) 的 ...
- dart 数组去重
List list = ['1','2','3','3']; list = list.toSet().toList();
- MES生产制造管理系统-BI看板 MES大屏看板
可视化看板最主要的目的是为了将生产状况透明化,让大家能够快速了解当前的生产状况以及进度,通过大数据汇总分析,为管理层做决策提供数据支撑,看板数据必须达到以下基本要求: 数据准确--真实反映生产情况 数 ...
- 【忍者算法】从生活到代码:解密链表大数相加的美妙算法|LeetCode第2题"两数相加"
从生活到代码:解密链表大数相加的美妙算法 从超市收银说起 想象你是一个超市收银员,正在计算两位顾客的购物总和.每位顾客的商品都按照从个位到高位的顺序摆放(比如54元就是先放4元商品,再放50元商品). ...
- 数字先锋 | 天翼云xDeepSeek,赋能东莞开启智慧政务新篇章!
人工智能浪潮奔涌 DeepSeek堪称"全能战士" 在各行各业疯狂"上分" 特别是在政务领域 其以强大的智能问答 公文写作.数据分析等能力 为政务服务按下了&q ...
- Shell - 集群监控脚本合集
node_heart_check.sh #!/bin/bash scriptPath=$(dirname "$0") for ip in `cat /etc/hosts | gre ...
- 基于C语言实现UDP服务器
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,适用于对实时性有较高要求的应用场景,如视频流传输.语音通信.在线游戏等.与TCP不同,UDP不保证数据的 ...
- Shell脚本实现服务器多台免密
简介 本脚本(auto_ssh_batch.sh)用于在多台主机之间快速配置SSH免密登录,并支持远程传输脚本/文件及执行命令.通过 pass 文件提供统一认证凭据,通过 nodes 文件定义目标主机 ...
- Docker 1.12 :认识 Swarm 模式下的节点崩溃处理
Posted on 2016年7月25日 上周小编为大家推荐了<Docker 1.12:用 Swarm 模式创建 Swarm 集群>,本周我们将深入为大家解读 1.12 版本 Docker ...






