大家好,我是编程熊。
往期我们一起学习了《线性表》相关知识。
本期我们一起学习二叉树,二叉树的问题,大多以递归为基础,根据题目的要求,在递归过程中记录关键信息,进而解决问题。
如果还未学习递归的同学,编程熊后续会讲解递归,建议学习递归后再来做二叉树相关题目,但并不影响学习二叉树基础知识部分。
本文将从以下几个方面展开,学习完可以解决面试常见的二叉树问题。
顾名思义,二叉树的每个节点最多有两个子节点,下图展示了常见的二叉树。
二叉树是由许多节点组成,节点有数据域、指针域,节点之间通过指针链接,形成一棵树。
二叉树节点的定义方式(C++代码):
struct TreeNode { // 节点数据 int val; // 指向左儿子的指针 TreeNode *left; // 指向右儿子的指针 TreeNode *right; // 构造函数 TreeNode(int x) : val(x), left(NULL), right(NULL) {} };
二叉树常见的存储方式有:
下图是链式存储的示意图。
下图是数组存储的示意图。
二叉树根据节点的分布位置、节点数值的排列方式,分为以下几种。
下面,我将从分别讨论以上几种二叉树特点。
满二叉树的特点有:
下图是一个满二叉树。
完全二叉树的特点有:
下图演示了一个完全二叉树。
平衡二叉树的特点有:
下图演示了一个平衡二叉树。
二叉搜索树的特点有:
下图演示了一个二叉搜索树。
二叉树遍历方式根据遍历节点的先后顺序,分为以下几种。
前序遍历节点的顺序是:根、左、右。
下面是前序遍历的伪代码。
// 伪代码 // 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);
下图以一棵树,分别演示了前序、中序、后续遍历的结果。
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
给定二叉树 [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)); } };
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 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); } };
给你二叉树的根节点 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); } };
给定一棵树的前序遍历 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); } };
我是编程熊,我们下期见。