编程熊讲解LeetCode算法《二叉树》
大家好,我是编程熊。
往期我们一起学习了《线性表》相关知识。
本期我们一起学习二叉树,二叉树的问题,大多以递归为基础,根据题目的要求,在递归过程中记录关键信息,进而解决问题。
如果还未学习递归的同学,编程熊后续会讲解递归,建议学习递归后再来做二叉树相关题目,但并不影响学习二叉树基础知识部分。
本文将从以下几个方面展开,学习完可以解决面试常见的二叉树问题。
二叉树概述和定义
顾名思义,二叉树的每个节点最多有两个子节点,下图展示了常见的二叉树。
二叉树的定义方式
二叉树是由许多节点组成,节点有数据域、指针域,节点之间通过指针链接,形成一棵树。
二叉树节点的定义方式(C++代码):
struct TreeNode {
// 节点数据
int val;
// 指向左儿子的指针
TreeNode *left;
// 指向右儿子的指针
TreeNode *right;
// 构造函数
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
二叉树存储方式
二叉树常见的存储方式有:
- 链式存储: 链表的思想,通过指针定位。
- 数组存储: 数组的下标也是一个索引,可以定位到节点,数组存储的数据即为节点数据。
下图是链式存储的示意图。
下图是数组存储的示意图。
二叉树分类
二叉树根据节点的分布位置、节点数值的排列方式,分为以下几种。
- 满二叉树
- 完全二叉树
- 平衡二叉树
- 二叉搜索树
下面,我将从分别讨论以上几种二叉树特点。
满二叉树
满二叉树的特点有:
- 除最后一层无子节点外,其余每层的节点都有两个子节点。
下图是一个满二叉树。
完全二叉树
完全二叉树的特点有:
- 至多只有最后一的没有被填充满,其余每层都被填充满。
- 如果最后一层没有填充满,那么所有的节点都在最后一层左边的位置上。
下图演示了一个完全二叉树。
平衡二叉树
平衡二叉树的特点有:
- 空树 或 左右两个子树的高度差 的绝对值不超过1。
- 左右两个子树都是一个平衡二叉树。
下图演示了一个平衡二叉树。
二叉搜索树
二叉搜索树的特点有:
- 若左子树不空,则左子树上所有结点的值均小于它的根结点的值。
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
下图演示了一个二叉搜索树。
二叉树遍历方式
二叉树遍历方式根据遍历节点的先后顺序,分为以下几种。
- 前序遍历
- 中序遍历
- 后序遍历
前序遍历
前序遍历节点的顺序是:根、左、右。
下面是前序遍历的伪代码。
// 伪代码
// root left right
// 遍历根
dfs(root);
// 遍历左节点
dfs(left);
// 遍历右节点
dfs(right);
中序遍历
中序遍历节点的顺序是:左、根、右。
下面是中序遍历的伪代码。
// 伪代码
// 顺序: left root right
// 遍历左节点
dfs(left);
// 遍历根
dfs(root);
// 遍历右节点
dfs(right);
后序遍历
后序遍历节点的顺序是:左、右、根。
下面是后序遍历的伪代码。
// 伪代码
// 顺序: left right root
// 遍历左节点
dfs(left);
// 遍历右节点
dfs(right);
// 遍历根
dfs(root);
举例分析
下图以一棵树,分别演示了前序、中序、后续遍历的结果。
例题
LeetCode 104. 二叉树的最大深度
题意
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
题解
从根节点递归遍历每个节点,同时记录每个节点的深度。
每个点的深度等于父节点深度+1,根节点的深度设为1。
下图为求二叉树的最大深度的示意图。
代码
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr)
return 0;
return 1 + max(maxDepth(root->left), maxDepth(root->right));
}
};
LeetCode 110. 平衡二叉树
题意
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
示例
输入:root = [3,9,20,null,null,15,7]
输出:true
题解
递归记录每个节点左右子树的高度差,平衡二叉树定义: 左右两个子树的高度差的绝对值不超过1。若超过则不是平衡二叉树。
代码
class Solution {
public:
bool isBalanced(TreeNode* root) {
return dfs(root) >= 0;
}
int dfs(TreeNode* root) {
if (root == nullptr) {
return 0;
}
// 记录左子树的高度
int left_height = dfs(root->left);
// 记录右子树的高度
int right_height = dfs(root->right);
// 根据左右子树深度,判断是否满足平衡二叉树条件
if (abs(left_height - right_height) > 1 || left_height == -1 || right_height == -1) {
return -1;
}
// 返回当前节点的子树的最大深度
return 1 + max(left_height, right_height);
}
};
LeetCode 144. 二叉树的前序遍历
题意
给你二叉树的根节点 root
,返回它节点值的 前序 遍历。
示例
输入:root = [1,null,2,3]
输出:[1,2,3]
题解
根据前序遍历定义的遍历顺序,即根、左、右的顺序遍历二叉树。
代码
class Solution {
public:
vector<int> preorderTraversal(TreeNode *root) {
vector<int> ans;
dfs(root, ans);
return ans;
}
void dfs(TreeNode *root, vector<int> &ans) {
if (root == nullptr) {
return;
}
ans.push_back(root->val);
dfs(root->left, ans);
dfs(root->right, ans);
}
};
LeetCode 105. 从前序与中序遍历序列构造二叉树
题意
给定一棵树的前序遍历 preorder
与中序遍历 inorder
。请构造二叉树并返回其根节点。
示例
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
题解
前序遍历的顺序是: 根、左、右。因此前序遍历数组的第一个节点就是根,因此前序序列可以快速定位根。
中序遍历的顺序是: 左、根、右。根据前序序列找到根,可以将左子树和右子树 分割 开,同时可以知道左子树的节点数量和右子树的节点数量。
下图是思路示意图。
因此我们可以利用前序遍历快速定位根,再利用中序遍历将左子树和右子树 分割 开,并知道左右子树的节点数量。
代码实现上,可以通过递归不断的划分左右子树,左右子树分别返回其根节点,就是根节点的左儿子、右儿子,细节可以看下面代码,很容易理解。
代码
// 代码来源于下面链接, 根据自己偏好, 进行了修改。
// https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/cong-qian-xu-yu-zhong-xu-bian-li-xu-lie-gou-zao-9/
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:
unordered_map<int, int> index;
TreeNode* myBuildTree(vector<int>& preorder, vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return nullptr;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = index[preorder[preorder_root]];
// 先把根节点建立出来
TreeNode* root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int size = preorder.size();
// 构造哈希映射,快速定位中序遍历的根节点
for (int i = 0; i <= size - 1; ++i) {
index[inorder[i]] = i;
}
return myBuildTree(preorder, inorder, 0, size - 1, 0, size - 1);
}
};
习题推荐
- LeetCode 98. 验证二叉搜索树
- LeetCode 637. 二叉树的层平均值
- LeetCode 112. 路径总和
- LeetCode 543. 二叉树的直径
- LeetCode 106. 从中序与后序遍历序列构造二叉树
我是编程熊,我们下期见。
编程熊讲解LeetCode算法《二叉树》的更多相关文章
- ACM金牌选手讲解LeetCode算法《栈和队列的高级应用》
大家好,我是编程熊,双非逆袭选手,字节跳动.旷视科技前员工,ACM金牌,保研985,<ACM金牌选手讲解LeetCode算法系列>作者. 上一篇文章讲解了<线性表>中的数组.链 ...
- ACM金牌选手讲解LeetCode算法《哈希》
大家好,我是编程熊. 往期文章介绍了<线性表>中的数组.链表.栈.队列,以及单调栈和滑动窗口. ACM金牌选手讲解LeetCode算法<线性表> ACM金牌选手讲解LeetCo ...
- Leetcode算法【114. 二叉树展开为链表】
上周通过一位小伙伴,加入了一个氛围很好的小群,人不多,但是大家保持着对知识的渴望,让我很感动. 我自己也有一个群,人数也不多,但是能真正互动起来一起学习,一起进步的,还是太少.所以,现在也在学习如何让 ...
- leetcode算法: Find Bottom Left Tree Value
leetcode算法: Find Bottom Left Tree ValueGiven a binary tree, find the leftmost value in the last row ...
- LeetCode算法题-Minimum Distance Between BST Nodes(Java实现-四种解法)
这是悦乐书的第314次更新,第335篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第183题(顺位题号是783).给定具有根节点值的二叉搜索树(BST),返回树中任何两个 ...
- LeetCode算法题-Reach a Number(Java实现)
这是悦乐书的第310次更新,第331篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第179题(顺位题号是754).你站在无限数字线的0号位置.在目的地有个target.在 ...
- LeetCode算法题-Search in a Binary Search Tree(Java实现)
这是悦乐书的第295次更新,第314篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第163题(顺位题号是700).给定一个二叉搜索树(BST)的和正整数val. 你需要在 ...
- LeetCode算法题-Longest Univalue Path(Java实现)
这是悦乐书的第290次更新,第308篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第158题(顺位题号是687).给定二叉树,找到路径中每个节点具有相同值的最长路径的长度 ...
- LeetCode算法题-Second Minimum Node In a Binary Tree(Java实现)
这是悦乐书的第285次更新,第302篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第153题(顺位题号是671).给定非空的特殊二叉树,其由具有非负值的节点组成,其中该树 ...
随机推荐
- LeSS 的诞生(一):大规模团队该何去何从
<敏捷宣言>发布后,"敏捷"被越来越多的小型开发团队认可.与此同时,另一个问题也逐渐暴露了出来:以 Scrum 为首的敏捷方法论对那些大规模的开发团队并不友好. 基于此 ...
- liunx驱动之字符设备的注册
上一篇文章学习了如何编写linux驱动,通过能否正常加载模块进行验证是否成功,有做过liunx应用开发的小伙伴都知道驱动会在'/dev'目录下以文件的形式展现出来,所以只是能加载驱动模块不能算是完成驱 ...
- 『无为则无心』Python函数 — 25、Python中的函数
目录 1.函数的使用 (1)定义函数 (2)调用函数 (3)使用函数的注意事项 2.函数的参数 3.实参的类型 Python函数的说明: Python中函数的应用非常广泛,前面章节中我们已经接触过多个 ...
- 前端 | Vue 路由返回恢复页面状态
需求场景:首页搜索内容,点击跳转至详情页,页面后退返回主页,保留搜索结果. 方案:路由参数:路由守卫 需求描述 在使用 Vue 开发前端的时候遇到一个场景:在首页进行一些数据搜索,点击搜索结果进入详情 ...
- python二进制读写及特殊码同步
python对二进制文件的操作需要使用bytes类,直接写入整数是不行的,如果试图使用f.write(123)向文件中以二进制写入123,结果提示参数不是bytes类型. import os impo ...
- [心得体会]mysql复习
1. 进入企业需要注意的事情 (1) 查看测试服和本地的mysql版本是否一致(2) 确认sql_mode是否和线上版本一致 show VARIABLES LIKE 'sql_mode'; (3) m ...
- phpredis中文手册
本文是参考<redis中文手册>,将示例代码用php来实现,注意php-redis与redis_cli的区别(主要是返回值类型和参数用法). 目录(使用CTRL+F快速查找命令): Key ...
- 【Azure 应用服务】Azure Function App使用SendGrid发送邮件遇见异常消息The operation was canceled,分析源码逐步最终源端
问题描述 在使用Azure Function App的SendGrid Binging功能,调用SendGrid服务器发送邮件功能时,有时候遇见间歇性,偶发性异常.在重新触发SendGrid部分的Fu ...
- ARTS第六周
第六周.后期补完,太忙了. 1.Algorithm:每周至少做一个 leetcode 的算法题2.Review:阅读并点评至少一篇英文技术文章3.Tip:学习至少一个技术技巧4.Share:分享一篇有 ...
- ARTS第三周
第三周.上周欠下了 赶紧补上,糟糕了 还有第四篇也得加紧了 难受. 1.Algorithm:每周至少做一个 leetcode 的算法题2.Review:阅读并点评至少一篇英文技术文章3.Tip:学习至 ...