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

news2024/12/28 11:52:34

代码随想录算法训练营第十七天|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.coloradmin.cn/o/1449603.html

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

相关文章

C++集群聊天服务器 nginx+redis安装 笔记 (中)

一、nginx安装 nginx: download 下载nginx安装包 hehedalinux:~/package$ tar -zvxf nginx-1.24.0.tar.gz nginx-1.24.0/ nginx-1.24.0/auto/ nginx-1.24.0/conf/ nginx-1.24.0/contrib/ nginx-1.24.0/src/ nginx-1.24.0/configure nginx-1.24.0/LICENSE nginx-1.24.0/README…

【并发编程】ThreadPoolExecutor类

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;并发编程⛺️稳重求进&#xff0c;晒太阳 ThreadPoolExecutor 1) 线程池状态 ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态&#xff0c;低 29 位表示线程数量 状态名 高三位 …

详解tomcat中的jmx监控

目录 1.概述 2.如何开启tomcat的JMX 3.tomcat如何实现JMX的源码分析 1.概述 本文是博主JAVA监控技术系列文章的第二篇&#xff0c;前面一篇文章中我们介绍了JAVA监控技术的基石——jmx&#xff1a; 【JMX】JAVA监控的基石-CSDN博客 本文我们将从使用和源码实现两个方面聊…

C语言习题----不同版本的差别

这个程序数组越界&#xff0c;但是结果是死循环&#xff1b; &#xff08;1&#xff09;死循环的这种情况只会在debug--x86的版本才会出现&#xff0c;其他版本不会出现&#xff1b;这种情况会在特定的情况下发生&#xff0c;和环境有和大的关系&#xff0c;不同的编译器对于内…

(三)【Jmeter】以模板创建并剖析第一个JMeter测试计划

部署好”Jmeter“。后续操作以windows操作系统为主&#xff0c;在环境变量path中增加jmeter环境变量&#xff1a; 启动Jmeter 执行命令WINR,在输入框输入”jmeter“ 后台日志如下&#xff1a; StatusConsoleListener 该监听器在以后会被废弃掉 命令行格式&#xff1a; jmet…

TCP和UDP相关问题(重点)——7.TCP的流量控制怎么实现的?

流量控制就是在双方通信时&#xff0c;发送方的速率和接收方的速率不一定是相等的&#xff0c;如果发送方发送的太快&#xff0c;接收方就只能把数据先放到接收缓冲区中&#xff0c;如果缓冲区都满了&#xff0c;那么处理不过来就只能丢弃&#xff0c;所以需要控制发送方的速率…

CCF编程能力等级认证GESP—C++2级—20231209

CCF编程能力等级认证GESP—C2级—20231209 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09;判断题&#xff08;每题 2 分&#xff0c;共 20 分&#xff09;编程题 (每题 25 分&#xff0c;共 50 分)小杨做题小杨的 H 字矩阵 参考答案单选题判断题编程题1编程题2 …

NLP快速入门

NLP入门 课程链接&#xff1a;https://www.bilibili.com/video/BV17K4y1W7yb/?p1&vd_source3f265bbf5a1f54aab2155d9cc1250219 参考文档链接1&#xff1a;NLP知识点&#xff1a;Tokenizer分词器 - 掘金 (juejin.cn) 一、分词 分词是什么&#xff1f; 每个字母都有对应…

easyx搭建项目-永七大作战(割草游戏)

永七大作战 游戏介绍&#xff1a; 永七大作战 游戏代码链接&#xff1a;永七大作战 提取码&#xff1a;ABCD 不想水文了&#xff0c;直接献出源码&#xff0c;表示我的诚意

【测接口试】JMeter接口关联测试

‍‍1 前言 我们来学习接口管理测试&#xff0c;这就要使用到JMeter提供的JSON提取器和正则表达式提取器了&#xff0c;下面我们来看看是如何使用的吧。 2 JSON提取器 1、添加JSON提取器 在线程组右键 > 添加 > 后置处理器 > JSON提取器 2、JSON提取器参数说明 N…

代码随想录算法训练营第二十六天|332.重新安排行程,51. N皇后,37. 解数独,总结

系列文章目录 代码随想录算法训练营第一天|数组理论基础&#xff0c;704. 二分查找&#xff0c;27. 移除元素 代码随想录算法训练营第二天|977.有序数组的平方 &#xff0c;209.长度最小的子数组 &#xff0c;59.螺旋矩阵II 代码随想录算法训练营第三天|链表理论基础&#xff…

Json-序列化字符串时间格式问题

序列化字符串时间格式问题 一、项目场景二、问题描述三、解决方案 一、项目场景 最近C#中需要将实体进行json序列化&#xff0c;使用了Newtonsoft.Json public static void TestJson(){DataTable dt new DataTable();dt.Columns.Add("Age", Type.GetType("Sys…

(13)Hive调优——动态分区导致的小文件问题

前言 动态分区指的是&#xff1a;分区的字段值是基于查询结果自动推断出来的&#xff0c;核心语法就是insertselect。 具体内容指路文章&#xff1a; https://blog.csdn.net/SHWAITME/article/details/136111924?spm1001.2014.3001.5501文章浏览阅读483次&#xff0c;点赞15次…

codechef121轮(A-G)

codechef121轮&#xff08;A-G)题解 ⭐️A.Leg Space 题目&#xff1a; &#x1f31f;题解&#xff1a; 很简单&#xff0c;比较就行 代码&#xff1a; #include <bits/stdc.h> using namespace std; int main() {// your code goes here int n,m;cin>>n>>…

尚硅谷最新Node.js 学习笔记(二)

目录 五、HTTP协议 5.1、概念 5.2、请求报文的组成 5.3、HTTP 的请求行 5.4、HTTP 的请求头 5.5、HTTP 的请求体 5.6、响应报文的组成 5.7、创建HTTP服务 操作步骤 测试 注意事项 5.8、浏览器查看 HTTP 报文 查看请求行和请求头 查看请求体 查看URL查询字符串 …

【C语言】指针练习篇(上),深入理解指针---指针和数组练习题和sizeof,strlen的对比【图文讲解,详细解答】

欢迎来CILMY23的博客喔&#xff0c;本期系列为【C语言】指针练习篇&#xff08;上&#xff09;&#xff0c;深入理解指针---指针数组练习题和sizeof&#xff0c;strlen的对比【图文讲解,详细解答】&#xff0c;图文讲解指针和数组练习题&#xff0c;带大家更深刻理解指针的应用…

企业计算机服务器中了mkp勒索病毒怎么办?Mkp勒索病毒解密处理

随着网络技术的不断发展&#xff0c;企业的生产运营也加大了步伐&#xff0c;网络为企业的生产运营提供了强有力保障&#xff0c;但网络是一把双刃剑&#xff0c;给企业带来便利的同时也为企业带来了严重的数据威胁。春节期间&#xff0c;云天数据恢复中心接到很多企业的值班人…

UnityShader——04渲染流水

渲染流水 GPU应用阶段 把数据加载到显存中设置渲染状态调用DrawCall 将渲染所需数据从硬盘加载到内存中&#xff0c;网格纹理等数据又被加载到显存中&#xff08;一般加载到显存后内存中的数据就会被移除&#xff09; 这些状态定义了场景中的网格是怎样被渲染的。例如&#xf…

AI - 碰撞避免算法分析(ORCA)

对比VO/RVO ORCA算法检测碰撞的原理和VO/RVO基本一样的&#xff0c;只是碰撞区域的计算去掉了一定时间以外才可能发生的碰撞&#xff0c;因此碰撞区域的扇形去掉了前面的部分&#xff0c;由圆锥头变成了个圆 另一个最主要的区别是&#xff0c;求新的速度&#xff0c;是根据相…

LLM Visualization可视化

可视化演示网站&#xff1a;https://bbycroft.net/llm 视频解释&#xff1a;https://www.bilibili.com/video/BV1hZ4y1E7DZ/?spm_id_from333.788&vd_sourcecc2da879c044059d9838f660bcaf4664 欢迎使用Markdown编辑器 你好&#xff01; 这是你第一次使用 Markdown编辑器 …