代码随想录算法训练营第十七天|Leetcode110 平衡二叉树、Leetcode257 二叉树的所有路径、Leetcode404 左叶子之和

代码随想录算法训练营第十七天|Leetcode110 平衡二叉树、Leetcode257 二叉树的所有路径、Leetcode404 左叶子之和

  • ● Leetcode110 平衡二叉树
    • ● 解题思路
    • ● 代码实现
  • ● Leetcode257 二叉树的所有路径
    • ● 解题思路
    • ● 代码实现
  • ● Leetcode404 左叶子之和
    • ● 解题思路
    • ● 代码实现

● Leetcode110 平衡二叉树

题目链接:Leetcode110 平衡二叉树
视频讲解:代码随想录|平衡二叉树
题目描述:给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 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)明确递归函数的参数和返回值:
根据平衡二叉树定义

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

我们需要获取左右子树的高度,然后对其做差才能判断二叉树是否为平衡二叉树。因此我们自定义一个获取二叉树高度的函数,返回值为二叉树高度int,传入参数必然是传入根节点Treenode* root

int getHeight(TreeNode* root)

(2)确定递归终止条件:
当传入结点为空时,递归就需要向上返回;

//终止条件
if(!root) return 0;

(3)确定单层递归的逻辑:
在之前的文章中已经讨论过对于高度和深度使用哪种遍历方式最优,因为我们需要将子树的平衡结果返回给上一结点,所以我们必然需要先遍历左右结点随后中结点才能达到目的,因此我们在单层递归中使用后序遍历
我们需要先获取以左右结点为根的子树高度,随后对其做差取绝对值和1进行比较,只有result <= 1时才是平衡二叉树。

倘若在某一处子树的返回值为-1时,即已经可以证明整个二叉树一定不是平衡二叉树,直接返回即可。

方法二:迭代
首先,我们需要使用栈模拟二叉树的后序遍历自定义获取二叉树高度的函数,帮助我们获取每一个结点的高度;
然后在主函数中使用栈遍历每一个结点判断是否为平衡二叉树。
看图感悟过程吧。
迭代

● 代码实现

方法一:递归

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:int getHeight(TreeNode* root){//终止条件if(!root) return 0;//左int leftHeight = getHeight(root->left);if(leftHeight == -1) return -1;//右int rightHeight = getHeight(root->right);if(rightHeight == -1) return -1;//中int result;if(abs(rightHeight - leftHeight) > 1) result = -1;else{result = 1 + max(leftHeight, rightHeight);}return result;}bool isBalanced(TreeNode* root) {return getHeight(root) == -1 ? false : true;}
};

方法二:迭代

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:int getHeight(TreeNode* node){stack<TreeNode*> st;if(node) st.push(node);int depth = 0, result = 0;while(!st.empty()){TreeNode* node = st.top();if(node){st.pop();st.push(node);st.push(nullptr);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;}bool isBalanced(TreeNode* root) {if(!root) return true;stack<TreeNode*> st;st.push(root);while(!st.empty()){TreeNode* node = st.top(); st.pop();int leftHeight = getHeight(node->left);int rightHeight = getHeight(node->right);if(abs(rightHeight - leftHeight) > 1) return false;if(node->right) st.push(node->right);if(node->left) st.push(node->left);}return true;}
};

● Leetcode257 二叉树的所有路径

题目链接:Leetcode257 二叉树的所有路径
视频讲解:代码随想录|二叉树的所有路径
题目描述:给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。

示例 1:
示例一
输入:root = [1,2,3,null,5]
输出:[“1->2->5”,“1->3”]
示例 2:
输入:root = [1]
输出:[“1”]

● 解题思路

方法一:递归
本题需要使用回溯进行解决,因为当我们遍历完一条完成路径之后,需要通过回溯才能重新进入另外一条路径,和迷宫是一个道理。

(1)确定递归函数参数和返回值:
该函数不需要返回值,传入参数需要包括遍历结点,存储路径的容器path以及存储最终结果的容器result

void traversal(TreeNode* node, vector<int>& path, vector<string>& result)

递归
(2)确定终止条件:
我们需要记录从根结点开始的每一条路径,在这里我们不需要遍历到空结点,因此当遍历到叶子结点时递归终止;

if(!node->left && !node->right)
{//将路径结点的值转化为string类型string sPath; for(int i = 0; i < path.size() - 1; i++) //将前n - 1个元素转换为"val->"字符串形式{sPath += to_string(path[i]);sPath += "->";}sPath += to_string(path[path.size() - 1]); //加入叶子结点result.push_back(sPath); //将转换后的字符串插入result中return;
}

(3)确定单层递归逻辑:
我们需要从根结点不断向下遍历获取二叉树的路径,因此前序遍历更合适。
但需要注意,对于中的处理需要放在终止条件之前,因为如果将其放在终止条件之后,无法在path中加入叶子结点,就没办法返回正确结点。

方法二:迭代
我们使用一个栈保存树遍历的结点,另一个栈保存遍历的路径。
迭代

● 代码实现

方法一:递归

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:void traversal(TreeNode* node, vector<int>& path, vector<string>& result){path.push_back(node->val);//中if(!node->left && !node->right){//将路径结点的值转化为string类型string sPath; for(int i = 0; i < path.size() - 1; i++) //将前n - 1个元素转换为"val->"字符串形式{sPath += to_string(path[i]);sPath += "->";}sPath += to_string(path[path.size() - 1]); //加入叶子结点result.push_back(sPath); //将转换后的字符串插入result中return;}//左if(node->left){traversal(node->left, path, result); //递归path.pop_back(); //回溯}//右if(node->right){traversal(node->right, path, result); //递归path.pop_back(); //回溯}}vector<string> binaryTreePaths(TreeNode* root) {vector<int> path; //存放一条路径vector<string> result; //将路径全部放在返回结果if(!root) return result;traversal(root, path, result);return result;}
};

上面的能够充分体现了回溯,对其进行简化:

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:void traversal(TreeNode* node, string path, vector<string>& result){//中path += to_string(node->val);//终止条件if(!node->left && !node->right){result.push_back(path);return;}//左if(node->left) traversal(node->left, path + "->", result);//右if(node->right) traversal(node->right, path + "->", result);}vector<string> binaryTreePaths(TreeNode* root) {string path; //存放一条路径vector<string> result; //将路径全部放在返回结果if(!root) return result;traversal(root, path, result);return result;}
};

以上代码就难以看出来回溯,简洁代码传参的path需要使用string path,不能使用引用,否则无法达到回溯的效果;简洁代码中的回溯就隐藏在traversal(cur->left, path + “->”, result);中的 path + “->”。 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了。
我们可以将简洁代码展开,只将path += "->"展开到函数无法达到回溯,因为我们使用的拷贝赋值,而不是引用,因此需要将"->"分别pop_back()出来。

解释:
参数使用的是 string path,这里并没有加上引用& ,即本层递归中为path + 该节点数值,但该层递归结束,上一层path的数值并不会受到任何影响。
vector<int>& path是会拷贝地址的,所以本层递归逻辑如果有path.push_back(cur->val); 就一定要有对应的 path.pop_back()
那为什么不去定义一个 string& path 这样的函数参数呢,然后也可能在递归函数中展现回溯的过程,但关键在于,path += to_string(cur->val); 每次是加上一个数字,这个数字如果是个位数,那好说,就调用一次path.pop_back(),但如果是 十位数,百位数,千位数呢? 百位数就要调用三次path.pop_back(),才能实现对应的回溯操作,这样代码实现就太冗余了。
所以第一个代码版本中才使用 vector 类型的path,这样能体现代码中回溯的操作。vector类型的path,不管每 路径收集的数字是几位数,总之一定是int,所以就一次 pop_back就可以。

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:void traversal(TreeNode* node, string path, vector<string>& result){//中path += to_string(node->val);//终止条件if(!node->left && !node->right){result.push_back(path);return;}//左if(node->left){path += "->";traversal(node->left, path, result);path.pop_back(); //回溯">"path.pop_back(); //回溯"-"}//右if(node->right){path += "->";traversal(node->right, path, result);path.pop_back(); //回溯">"path.pop_back(); //回溯"-"}}vector<string> binaryTreePaths(TreeNode* root) {string path; //存放一条路径vector<string> result; //将路径全部放在返回结果if(!root) return result;traversal(root, path, result);return result;}
};

方式二:迭代

class Solution {
public: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;}
};

● Leetcode404 左叶子之和

题目链接:Leetcode404 左叶子之和
视频讲解:代码随想录|左叶子之和
题目描述:给定二叉树的根节点 root ,返回所有左叶子之和。

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

● 解题思路

方法一:递归
(1)确定函数参数和返回值:
对于返回左叶子之和,返回值为整数int,传入参数为根结点即可;
(2)确定递归终止条件:
当传入结点为空或者传入结点为叶子结点的时候,就没有继续向下递归的必要;
(3)确定单层遍历逻辑:
我们首先需要明确遍历到哪?
如果我们遍历到叶子结点的时候,我们无法将叶子结点的值返回给其父结点,因此我们只需要遍历到其父结点,通过node->left->left(right)是否为空判断叶子结点即可。

因为我们需要将左右子树的左叶子之和返回给其父结点,因此需要先对左右遍历,然后在处理中时将左右子树的左叶子结点加和返回给自身即可,因此其单层遍历逻辑使用后序遍历

方法二:迭代
对于迭代,使用前中后序均可。只需要判断是否为左叶子结点后将其值累加即可。

● 代码实现

方法一:递归

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:int sumOfLeftLeaves(TreeNode* root) {//终止条件TreeNode* node = root;if(!node) return 0;if(!node->left && !node->right) return 0;//左int leftValue = sumOfLeftLeaves(node->left);if(node->left && !node->left->left && !node->left->right){leftValue = node->left->val;}//右int rightVale = sumOfLeftLeaves(node->right);//中int sum = leftValue + rightVale;return sum;}
};

方法二:迭代

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}* };*/
class Solution {
public:int sumOfLeftLeaves(TreeNode* root) {if(!root) return 0;stack<TreeNode*> st;int result = 0;st.push(root);while(!st.empty()){TreeNode* node = st.top(); st.pop();//中if(node->left && !node->left->left && !node->left->right){result += node->left->val;}//左if(node->left) st.push(node->left);//右if(node->right) st.push(node->right);}return result;}
};

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

esp8266-01s WIFI模块使用(一)- AT指令

时间记录&#xff1a;2024/2/15 一、注意事项 &#xff08;1&#xff09;使用英文双引号表示字符串数据 &#xff08;2&#xff09;默认波特率115200 &#xff08;3&#xff09;AT指令以“\r\n”结尾 &#xff08;4&#xff09;3.3V电源接口先连接单片机的3.3V&#xff0c;如…

7 大 Android 数据恢复软件,可轻松找回丢失的数据

每年&#xff0c;由于各种原因&#xff0c;数百万人从他们的 Android 设备中丢失数据。它可能像意外删除文件一样简单&#xff0c;也可能像系统崩溃一样复杂。在这种情况下&#xff0c;拥有高效的数据恢复工具可以证明是救命稻草。Mac 用户尤其需要找到与其系统兼容的软件。好消…

AtCoder Beginner Contest 335 (Sponsored by Mynavi) --- F - Hop Sugoroku -- 题解

目录 F - Hop Sugoroku 题目大意&#xff1a; 思路解析&#xff1a; 代码实现&#xff1a; F - Hop Sugoroku 题目大意&#xff1a; 思路解析&#xff1a; 容易想到这是一个dp题&#xff0c;然后初始转移方程为&#xff1a; 如果当a[i] 较大时&#xff0c;时间复杂度为 O(N…

ElasticSearch分词器和相关性详解

目录 ES分词器详解 基本概念 分词发生时期 分词器的组成 切词器&#xff1a;Tokenizer 词项过滤器&#xff1a;Token Filter 停用词 同义词 字符过滤器&#xff1a;Character Filter HTML 标签过滤器&#xff1a;HTML Strip Character Filter 字符映射过滤器&#x…

17.JS中的object、map和weakMap

1.object和map的区别 2.weakMap和map的区别 &#xff08;1&#xff09;Map本质上就是键值对的集合&#xff0c;但是普通的Object中的键值对中的键只能是字符串。而ES6提供的Map数据结构类似于对象&#xff0c;但是它的键不限制范围&#xff0c;可以是任意类型&#xff0c;是一…

使用bpmn-js 配置颜色

本篇文章介绍如何使用bpmn-js给图例配置颜色。该示例展示了如何向BPMN图添加颜色的多种不同方法。 通过层叠设置颜色 这种方式比较简单&#xff0c;直接通过设置图片的CSS层叠样式就可实现。 .highlight-overlay {background-color: green; /* color elements as green */opa…

【算法设计与分析】反转链表 ||

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 题目 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表…

Attention Is All Your Need论文翻译

0.摘要 这个统治序列转换模型是基于复杂循环或者卷积神经网络&#xff0c;它包含编码器和解码器。表现最好的模型也通过注意力机制来连接编码器和解码器。我们提出了一个新的简单网络架构——Transformer,它仅仅是是基于注意力机制&#xff0c;完全免去递推和卷积。在两个机器…

代码随想录算法训练营第十七天| 110.平衡二叉树、257. 二叉树的所有路径、 404.左叶子之和 (优先掌握递归)

110.平衡二叉树 刷题https://leetcode.cn/problems/balanced-binary-tree/description/文章讲解https://programmercarl.com/0110.%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.html视频讲解https://www.bilibili.com/video/BV1Ug411S7my/?vd_sourceaf4853e80f89e28094a5f…

Android14之Android Rust模块编译语法(一百八十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

仰暮计划|“后来经历过大饥荒,饿得吃树皮、挖野菜,天天吃不饱饭”

去姥姥家之前就给姥姥打电话说&#xff0c;要去采访姥姥&#xff0c;给她写回忆录&#xff0c;姥姥有些意外&#xff0c;开心地笑着又有些害羞。 姥姥出生于1940年&#xff0c;家中六个孩子&#xff0c;两个哥哥都是军人。姥姥小时候长得很漂亮&#xff0c;又总是生病&#xf…

前端秘法进阶篇之事件循环

目录 一.浏览器的进程模型 1.进程 2.线程 二.浏览器的进程和线程 1. 浏览器进程 2. 网络进程 3. 渲染进程 三.渲染主线程 四.异步 五.优先级 1. 延时队列&#xff1a; 2.交互队列&#xff1a; 3.微队列&#xff1a; 六.JS 的事件循环 附加:JS 中的计时器能做到精…