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

解释: 在这个二叉树中,有两个左叶子,分别是 915,所以返回 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. 完全二叉树的节点个数的更多相关文章

  1. 代码随想录算法训练营day17 | leetcode ● 110.平衡二叉树 ● 257. 二叉树的所有路径 ● 404.左叶子之和

    LeetCode 110.平衡二叉树 分析1.0 求左子树高度和右子树高度,若高度差>1,则返回false,所以我递归了两遍 class Solution { public boolean is ...

  2. 代码随想录算法训练营day16 | leetcode ● 104.二叉树的最大深度 559.n叉树的最大深度 ● 111.二叉树的最小深度 ● 222.完全二叉树的节点个数

    基础知识 二叉树的多种遍历方式,每种遍历方式各有其特点 LeetCode 104.二叉树的最大深度 分析1.0 往下遍历深度++,往上回溯深度-- class Solution { int deep ...

  3. x264代码剖析(十五):核心算法之宏块编码中的变换编码

    x264代码剖析(十五):核心算法之宏块编码中的变换编码 为了进一步节省图像的传输码率.须要对图像进行压缩,通常採用变换编码及量化来消除图像中的相关性以降低图像编码的动态范围.本文主要介绍变换编码的相 ...

  4. 代码随想录算法训练营day18 | leetcode 513.找树左下角的值 ● 112. 路径总和 113.路径总和ii ● 106.从中序与后序遍历序列构造二叉树

    LeetCode 513.找树左下角的值 分析1.0 二叉树的 最底层 最左边 节点的值,层序遍历获取最后一层首个节点值,记录每一层的首个节点,当没有下一层时,返回这个节点 class Solutio ...

  5. 代码随想录算法训练营day14 | leetcode 层序遍历 226.翻转二叉树 101.对称二叉树 2

    层序遍历 /** * 二叉树的层序遍历 */ class QueueTraverse { /** * 存放一层一层的数据 */ public List<List<Integer>&g ...

  6. 代码随想录算法训练营day07 | leetcode 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和

    LeetCode 454.四数相加II 分析1.0 这个最直接暴力法,不过过于暴力了,害怕.jpg 失误 读题理解失误:题目要求的是四元组的个数,读完题到我这里成了输出四元组,悲哉 分析2.0 记录有 ...

  7. 【leetcode 简单】 第九十四题 左叶子之和

    计算给定二叉树的所有左叶子之和. 示例: 3 / \ 9 20 / \ 15 7 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24 # Definition for a binary ...

  8. 十五个常用的jquery代码段【转】

    好的文章顶一个 回到顶部按钮 通过使用 jQuery 中的 animate 和 scrollTop 方法,你无需插件便可创建一个简单地回到顶部动画: 1 // Back to top 2 $('a.t ...

  9. 十五个常用的jquery代码段

    十五个常用的jquery代码段 回到顶部按钮 通过使用 jQuery 中的 animate 和 scrollTop 方法,你无需插件便可创建一个简单地回到顶部动画: 1 // Back to top ...

  10. python3.4学习笔记(二十五) Python 调用mysql redis实例代码

    python3.4学习笔记(二十五) Python 调用mysql redis实例代码 #coding: utf-8 __author__ = 'zdz8207' #python2.7 import ...

随机推荐

  1. Scratch教程:第1课认识Scratch

    一.Scratch来源 Scratch最初是由麻省理工学院媒体实验室的终身幼儿园研究小组的米切尔雷斯尼克和西摩于2003年发起的. 希望帮助所有的孩子"发现和跟随自己的激情和探索力,敢于尝试 ...

  2. CF145C Lucky Subsequence 题解

    首先,我们对这个幸运数进行分析,发现: \(10^9\) 以内只有 \(1023\) 个幸运数,即 \(\sum\limits_{i=0}^92^i\) 个. 考虑对幸运数和非幸运数分类讨论. 幸运数 ...

  3. IAP升级(STM32)

    IAP升级(STM32) IAP作用简述:将要升级的程序bin文件通过串口发送给STM32,STM32接收后存储到FLASH或者SRAM,用户通过事件(按键等)触发(也可延时自动触发)后将升级 文件夹 ...

  4. [Windows] 联发科秒开bl一键版(mtk)

    声明 不是所有的联发科都可 天机 8000 8100 9000等不行 已知 天机820 天机1000 mtk G90t 天机800 可以 其余自己测试 除了新款均可 第一步 下载软件 (是个压缩包需要 ...

  5. 有关C++程序设计基础的各种考题解答参考汇总

    早先年考研的主考科目正是[算法与数据结构],复习得还算可以.也在当时[百度知道]上回答了许多相关问题,现把他们一起汇总整理一下,供读者参考. [1] 原题目地址:https://zhidao.baid ...

  6. winform 实现太阳,地球,月球 运作规律https://www.cnblogs.com/axing/p/18762710

    无图眼吊(动图)    缘由 最近我太太在考公学习,给我出了两道高中地理知识的题目,把我问的一头雾水,题目是这样的 第一题 第二题 看到这两道题,当时大脑飞速运转,差点整个身体都在自转了,所以产生了个 ...

  7. CMake简单学习

    CMake 说明 cmake的定义是什么 ?-----高级编译配置工具 当多个人用不同的语言或者编译器开发一个项目,最终要输出一个可执行文件或者共享库(dll,so等等)这时候神器就出现了-----C ...

  8. MySQL-InnoDB行锁

    InnoDB的锁类型 InnoDB存储引擎支持行锁,锁类型有两种: 共享锁(S锁) 排他锁(X锁) S和S不互斥,其他均互斥. 除了这两种锁以外,innodb还支持一种锁,叫做意向锁. 那么什么是意向 ...

  9. 标准javabean

    1.javabean介绍 javabean,名为实体类,封装数据的类 前面我们写的类都是实体类,但我们写的不是标准的实体类 . 2.标准的javabean写法 如图 3.快捷键 一个成员变量就要写两个 ...

  10. 【Linux】5.7 Shell test命令

    Shell test 命令 Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值.字符和文件三个方面的测试. 1. 数值测试 参数 说明 -eq 等于则为真 -ne 不等于则为真 - ...