一、单值二叉树
1.1. | 解题思路 |
对二叉树进行前序遍历。首先判断根结点是否为NULL,若为NULL则直接返回true,若不为NULL则看是否存在左子树,若存在则判断左子树的数值与根结点的数值是否相同,不同则返回false,看完左子树后再看右子树,判断右子树的数值与根结点的数值是否相同,不同则返回false。如果上面都没有返回false,则说明到目前为止是符合要求的。还要递归根结点的左子树和右子树,继续执行以上操作。
1.2. | 题解代码 |
bool isUnivalTree(struct TreeNode* root) {
if (root == NULL)
{
return true;
}
if (root->left && root->left->val != root->val)
{
return false;
}
if (root->right && root->right->val != root->val)
{
return false;
}
return isUnivalTree(root->left) && isUnivalTree(root->right);
}
二、相同的树
OJ链接https://leetcode.cn/problems/same-tree/https://leetcode.cn/problems/same-tree/
2.1. | 解题思路 |
对二叉树进行前序遍历,先判断根结点,再判断左子树,再判断右子树。若两个二叉树都为空树,则两树相同,返回true;在判断过程中,两树对应的结点有一个是NULL,则两树不相同,返回false;在判断过程中,两树对应的结点都不为NULL,但对应的结点的val不同,则两树不相同,返回false。判断完根结点,再判断左子树,再判断右子树,如此递归,直到分别遍历完两颗二叉树为止。
2.2. | 题解代码 |
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
if (p == NULL && q == NULL)
{
return true;
}
if (p == NULL || q == NULL)
{
return false;
}
if (p->val != q->val)
{
return false;
}
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
三、对称二叉树
OJ链接https://leetcode.cn/problems/symmetric-tree/https://leetcode.cn/problems/symmetric-tree/
3.1. | 解题思路 |
判断根结点的左子树与右子树是否关于根结点轴对称。我们可以将此问题转化为:s将右子树整体翻转(使用递归,交换每个左子树的结点和右子树的结点),再判断左子树和翻转后的右子树是否相同,若相同则该树为对称二叉树,返回true;若不相同则该树不是对称二叉树,返回false。
3.2. | 题解代码 |
void ReverseTree(struct TreeNode* root)
{
if (root == NULL)
{
return;
}
struct TreeNode* tmp = root->left;
root->left = root->right;
root->right = tmp;
ReverseTree(root->left);
ReverseTree(root->right);
}
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
if (p == NULL && q == NULL)
{
return true;
}
if (p == NULL || q == NULL)
{
return false;
}
if (p->val != q->val)
{
return false;
}
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
bool isSymmetric(struct TreeNode* root) {
ReverseTree(root->right);
return isSameTree(root->left, root->right);
}
四、二叉树的前序遍历
4.1. | 解题思路 |
本题要返回一个数组,所以要动态开辟一块空间,开辟多大的空间我们可以通过计算题目给的二叉树有多少个结点,再用结点数乘一个结点的大小就可以求出要开辟多大的空间了。再通过递归式对该二叉树进行前序遍历,将遍历的根结点的val存放到开辟的空间中。要注意的是,因为使用的是递归式的前序遍历,而将根结点的val存放到数组中需要对数组的下标进行控制(即 i = 0,1,2,3,...),所以在调用前序遍历函数时要传一个控制数组下标的变量,且要传该变量的地址,这样在递归过程中才能正确的控制数组的下标。
4.2. | 题解代码 |
int TreeSize(struct TreeNode* root)
{
if (root == NULL)
{
return 0;
}
return TreeSize(root->left) + TreeSize(root->right) + 1;
}
void PrevOrder(struct TreeNode* root, int* p, int* pi)
{
if (root == NULL)
{
return;
}
p[*pi] = root->val;
(*pi)++;
PrevOrder(root->left, p, pi);
PrevOrder(root->right, p, pi);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
int* data = (int*)malloc(TreeSize(root) * sizeof(int));
int i = 0;
PrevOrder(root, data, &i);
*returnSize = i;
return data;
}
五、另一颗树的子树
5.1. | 解题思路 |
遍历二叉树root的每一个结点,并且判断每个结点当前root的子树与二叉树subRoot是否为相同的二叉树(可套用前面判断相同的树的代码),若当前子树与二叉树subRoot为相同的树,则可以判断subRoot是root的子树,返回true;若当前子树与二叉树subRoot不相同,则无法判断subRoot是否为root的子树,此时要继续递归当前子树的左子树和右子树继续进行上述判断,直到递归遍历到root的子树为NULL时,此时说明前面遍历过的root的子树中不存在与subRoot相同的树,所以当遍历到root的子树为NULL时返回false。值得注意的是,左子树中存在或右子树中存在与subRoot相同的树即可证明subRoot是root的子树,反之,左子树和右子树中都不存在与subRoot相同的树,才能证明subRoot不是root的子树。
5.2. | 题解代码 |
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
if (p == NULL && q == NULL)
{
return true;
}
if (p == NULL || q == NULL)
{
return false;
}
if (p->val != q->val)
{
return false;
}
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {
if (root == NULL)
{
return false;
}
if (isSameTree(root, subRoot))
{
return true;
}
return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}
六、二叉树遍历
6.1. | 解题思路 |
本题的解题步骤分为三大步。
第一步先根据题目给的字符串递归构建一颗二叉树。通过类前序遍历的方式来构建二叉树,在构建函数中将数组中的数据赋值给根结点(当数组元素为'#'时,不往二叉树中写入,继续顺序访问下一个元素,返回NULL),然后再递归构建左子树再递归构建右子树。要注意,在构建函数中我们要顺序遍历数组中的元素,需要控制数组的下标,所以在调用构造二叉树函数之前我们要传一个控制数组下标的变量,且要传它的地址,这样在后续的递归构造中才能正确的访问不同下标的元素。
第二步,对构造完成的二叉树进行递归式的中序遍历,并将遍历结果打印出来。
第三步,通过类后序遍历的方式实现一个销毁二叉树的函数,防止出现内存泄露的问题。
6.2. | 题解代码 |
#include<stdio.h>
#include<stdlib.h>
typedef char BTDataType;
typedef struct BTNode
{
BTDataType data;
struct BTNode* left;
struct BTNode* right;
}BTNode;
BTNode* CreateTree(BTDataType* p,int* pi)
{
if (p[*pi] == '#')
{
(*pi)++;
return NULL;
}
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
if (root == NULL)
{
perror("malloc fail");
return NULL;
}
root->data = p[*pi];
(*pi)++;
root->left = CreateTree(p, pi);
root->right = CreateTree(p, pi);
return root;
}
void BTInOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
BTInOrder(root->left);
printf("%c ", root->data);
BTInOrder(root->right);
}
void DestroyTree(BTNode* root)
{
if (root == NULL)
{
return;
}
DestroyTree(root->left);
DestroyTree(root->right);
free(root);
}
int main()
{
BTDataType str[101] = { 0 };
scanf("%s", str);
int i = 0;
BTNode* root = CreateTree(str, &i);
BTInOrder(root);
DestroyTree(root);
root = NULL;
return 0;
}
七、根据二叉树创建字符串
7.1 | 解题思路 |
本题采用递归调用的方式解决。本题要分左子树和右子树两种情况讨论:1.左子树是否为空都要加上(),若左子树为空不加()则会导致二叉树结构歧义;2.右子树不为空则加(),为空则省略()。
7.2 | 题解代码 |
class Solution {
public:
string tree2str(TreeNode* root) {
if (root == nullptr)
{
return string();
}
string str;
str += to_string(root->val);
// 1.左子树不管为不为空都要加括号
if (root->left) // 左子树不为空
{
str += '(';
str += tree2str(root->left);
str += ')';
}
else if (root->left == nullptr && root->right) // 左子树为空,右子树不为空
{
str += "()";
}
// 2.右子树不为空则加括号,为空则省略
if (root->right)
{
str += '(';
str += tree2str(root->right);
str += ')';
}
return str;
}
};
八、二叉树的层序遍历Ⅰ
8.1 | 解题思路 |
本题是二叉树层序遍历的变形,看到层序遍历二叉树就要联想到借助队列来实现。
本题的难点主要在于层序遍历的过程中,不好判断当前这一层有多少个节点(题目并没有说该二叉树为满二叉树),所以难以判断在队列中要pop多少次当前这一层的结点才算pop完。而本题有个很巧妙的思路就是:创建一个变量levelSize来记录当前这一层要pop多少个元素,每从队列中pop一个元素,levelSize相应的要减减,pop的同时要将该元素的左孩子和右孩子(如果左孩子和右孩子存在的话)push入队,当levelSize减到0时,表明这一层的元素已经遍历完了,遍历完这一层元素的同时也将下一层的元素push到了队列中。
如此反复,直到队列为空时,就是层序遍历完整颗二叉树了。
8.2 | 题解代码 |
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> vv;
if (root == nullptr)
{
return vv;
}
queue<TreeNode*> q;
q.push(root);
int levelSize = 1;
while (!q.empty())
{
vector<int> vt;
while (levelSize--)
{
TreeNode* front = q.front();
vt.push_back(front->val);
q.pop();
if (front->left)
{
q.push(front->left);
}
if (front->right)
{
q.push(front->right);
}
}
vv.push_back(vt);
levelSize = q.size();
}
return vv;
}
};
九、二叉树的层序遍历Ⅱ
OJ链接https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/
9.1 | 解题思路 |
在上一题(二叉树的层序遍历Ⅰ) 的基础上在返回二维数组前,调用reverse()逆置一下二维数组即可。
9.2 | 题解代码 |
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
vector<vector<int>> vv;
if (root == nullptr)
{
return vv;
}
queue<TreeNode*> q;
q.push(root);
int levelSize = 1;
while (!q.empty())
{
vector<int> vt;
while (levelSize--)
{
TreeNode* front = q.front();
vt.push_back(front->val);
q.pop();
if (front->left)
{
q.push(front->left);
}
if (front->right)
{
q.push(front->right);
}
}
vv.push_back(vt);
levelSize = q.size();
}
reverse(vv.begin(), vv.end());
return vv;
}
};
十、二叉树的最近公共祖先
OJ链接https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/
10.1 | 解题思路 |
这一题有两种解法:
方法一:常规分析
找两个结点的最近公共祖先,本质在于判断两结点是否为当前结点的左子树结点和右子树结点。若是,则当前结点就是两结点的最近公共祖先;若不是,则递归其他的结点进行判断,以下分情况进行分析:
① p或q有一个结点为root (root == p || root == q)
直接返回root
② p和q分别是当前结点的左子树结点和右子树结点(或右子树结点和左子树结点)
返回当前结点
③ p和q都是当前结点的左子树结点
递归当前结点的左子树,重复上述判断
④ p和q都是当前结点的右子树结点
递归当前结点的右子树,重复上述判断
方法二:转换为,两链表相交找第一个公共结点问题
链表相交找第一个公共结点的详细解答方法八、相交链表(见该链接中的第八题)
解题的核心思想是一样的。
本题需要借助stack来存放从root到p的路径和root到q的路径,获取路径的函数可以使用递归来实现(具体参考题解代码)。
10.2 | 题解代码 |
方法一:
class Solution {
public:
bool isInTree(TreeNode* root, TreeNode* key)
{
if (root == nullptr)
{
return false;
}
return root == key || isInTree(root->left, key) || isInTree(root->right, key);
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr)
{
return nullptr;
}
if (root == p || root == q)
{
return root;
}
bool pInLeft = isInTree(root->left, p);
bool pInRight = !pInLeft;
bool qInLeft = isInTree(root->left, q);
bool qInRight = !qInLeft;
if ((pInLeft && qInRight) || (pInRight && qInLeft))
{
return root;
}
if (pInLeft && qInLeft)
{
return lowestCommonAncestor(root->left, p, q);
}
else
{
return lowestCommonAncestor(root->right, p, q);
}
}
};
方法二:
class Solution {
public:
bool getPath(TreeNode* root, TreeNode* key, stack<TreeNode*>& path)
{
if (root == nullptr)
{
return false;
}
path.push(root);
if (root == key)
{
return true;
}
if (getPath(root->left, key, path))
{
return true;
}
if (getPath(root->right, key, path))
{
return true;
}
path.pop();
return false;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
stack<TreeNode*> pPath;
stack<TreeNode*> qPath;
getPath(root, p, pPath);
getPath(root, q, qPath);
// 让q和p的路径长度一致
while (pPath.size() != qPath.size())
{
if (pPath.size() > qPath.size())
{
pPath.pop();
}
else
{
qPath.pop();
}
}
// p和q同时pop结点,找到公共祖先
while (pPath.top() != qPath.top())
{
pPath.pop();
qPath.pop();
}
return pPath.top();
}
};
十一、二叉搜索树与双向链表
11.1 | 解题思路 |
将二叉搜索树转换成有序链表,首先要想到中序遍历能得到二叉搜索树的有序结果。
本题借助中序遍历的思想来解决问题。在中序遍历的递归函数中定义一个prev用于标记前驱结点,cur用于标记当前结点。在中序遍历过程中让cur->left = prev,中序遍历的递归函数中形参prev的类型是引用,这样才能实现上一个结点的后继与cur连接起来,即prev->right = cur(只有当prev是引用时才能对上一个结点产生影响,具体需要结合代码进行分析)。
将二叉搜索树重新建立链接关系后,再找到链表的头结点(while (head->left) {head = head->left;}),最后返回链表头结点即可。
11.2 | 题解代码 |
class Solution {
public:
void InOrderConvert(TreeNode*& prev, TreeNode* cur)
{
if (cur == nullptr)
{
return;
}
InOrderConvert(prev, cur->left);
cur->left = prev;
if (prev)
{
prev->right = cur;
}
prev = cur;
InOrderConvert(prev, cur->right);
}
TreeNode* Convert(TreeNode* pRootOfTree) {
if (pRootOfTree == nullptr)
{
return pRootOfTree;
}
TreeNode* prev = nullptr;
// 利用中序遍历进行结点链接
InOrderConvert(prev, pRootOfTree);
// 找链表的头结点
TreeNode* head = pRootOfTree;
while (head->left)
{
head = head->left;
}
return head;
}
};