对于前、中、后序遍历,相信是大家学习二叉树首先掌握的内容,对于这里我会采用递归和非递归的写法来展示,这里首先展示递归写法,非递归在后续更新
原题传送门
思路分析.
代码详解.
class Solution {
private:
void preOrder(TreeNode* root, vector<int> &ret)
{
if(root == NULL)
return;
ret.push_back(root->val);
preOrder(root->left,ret);
preOrder(root->right,ret);
}
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ret;
preOrder(root,ret);
return ret;
}
};
看完C++版本是不是觉得这道题很简单,我再补充一个C语言版本的,我们来玩玩指针✒
//前序遍历
void PreOrder(struct TreeNode* root, int* a, int* pi)
{
if(!root) return;
a[(*pi)++] = root->val;
PreOrder(root->left,a,pi);
PreOrder(root->right,a,pi);
}
//求解二叉树大小
int TreeSize(struct TreeNode* root){
if(!root) return 0;
return TreeSize(root->left) + TreeSize(root->right) + 1;
}
int* preorderTraversal(struct TreeNode* root, int* returnSize){
//数组大小(输出型参数)
//1.获取当前二叉树大小
*returnSize = TreeSize(root);
//2.根据二叉树大小动态开辟数组存放遍历序列
int* a = (int *)malloc(sizeof(int) * (*returnSize));
//3.封装前序遍历
int i = 0;
PreOrder(root,a,&i);
return a;
}
returnSize
】的指针,而且返回值也是一个指针,哪有同学问这是啥,其实在C语言的很多代码中有个returnSize是很正常的,因为像LeetCode这种接口型的OJ题它后台内部是有代码写好了会调用你写好的这个接口函数,一般来说他会去获取你这个数组的大小,但是呢这里的返回值是【int*】,也就是一个数组,C语言还可以返回结构体,但是比较麻烦,因此这个【returnSize】其实是外界用来获取你函数中算出来的数组大小的,而且他那里传入的是一个地址,所以在函数内部你可以通过【解引用】的方式来修改这个大小*returnSize = TreeSize(root);
void PreOrder(struct TreeNode* root, int* a, int i)
{
if(!root) return;
a[i++] = root->val;
PreOrder(root->left,a,i);
PreOrder(root->right,a,i);
}
i == 2
,但是在回到根节点后当前层的【i】在放入1后即为1。然后来到root->right
就会在i == 1
的位置上放值,但是这个地方在左子树递归的时候就放了【2】,若是再放【3】的话就会造成覆盖。i == 2
这个位置上并没有添加进值,所以是一个无符号整型的随机值void PreOrder(struct TreeNode* root, int* a, int* pi)
{
if(!root) return;
a[(*pi)++] = root->val;
PreOrder(root->left,a,pi);
PreOrder(root->right,a,pi);
}
PreOrder(root,a,&i);
原题传送门
思路分析.
代码详解.
class Solution {
private:
void InOrder(TreeNode* root, vector<int>& ret)
{
if(root == NULL)
return;
InOrder(root->left, ret);
ret.push_back(root->val);
InOrder(root->right, ret);
}
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ret;
InOrder(root,ret);
return ret;
}
};
//中序遍历
void InOrder(struct TreeNode* root, int* a, int* pi)
{
if(!root) return;
InOrder(root->left,a,pi);
a[(*pi)++] = root->val;
InOrder(root->right,a,pi);
}
//求二叉树大小
int TreeSize(struct TreeNode* root)
{
if(!root) return 0;
return TreeSize(root->left) + TreeSize(root->right) + 1;
}
int* inorderTraversal(struct TreeNode* root, int* returnSize){
int i = 0;
//1.首先获取二叉树大小
*returnSize = TreeSize(root);
//2.动态开辟存放中序遍历的数组
int* a = (int *)malloc(sizeof(int) * (*returnSize));
//3.中序遍历
InOrder(root,a,&i);
return a;
}
原题传送门
代码详解.
class Solution {
private:
void PostOrder(TreeNode* root, vector<int>& ret)
{
if(root == NULL)
return;
PostOrder(root->left, ret);
PostOrder(root->right, ret);
ret.push_back(root->val);
}
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ret;
PostOrder(root,ret);
return ret;
}
};
//后序遍历
void PostOrder(struct TreeNode* root, int* a, int* pi)
{
if(!root) return;
PostOrder(root->left,a,pi);
PostOrder(root->right,a,pi);
a[(*pi)++] = root->val;
}
//求二叉树大小
int TreeSize(struct TreeNode* root)
{
if(!root) return 0;
return TreeSize(root->left) + TreeSize(root->right) + 1;
}
int* postorderTraversal(struct TreeNode* root, int* returnSize){
int i = 0;
//1.首先获取二叉树大小
*returnSize = TreeSize(root);
//2.动态开辟存放中序遍历的数组
int* a = (int *)malloc(sizeof(int) * (*returnSize));
//3.中序遍历
PostOrder(root,a,&i);
return a;
}
原题传送门
思路分析
代码详解
class Solution {
private:
void DFS(TreeNode* cur, vector<vector<int>>& ret, int dep)
{
if(cur == NULL) return;
if(ret.size() <= dep) //过结果集大小 《= 当前层深度,表明第一层访问本层结点
{
vector<int> vec;
vec.push_back(cur->val); //创建一个小结果集将其放入
ret.push_back(vec);
}
else
{
//若是结果集大小 > 当前层深度,表明已经访问过,直接加入大结果集的当前层小结果集中即可
ret[dep].push_back(cur->val);
}
//递归其左右子树,开始下一层的访问
DFS(cur->left, ret, dep + 1);
DFS(cur->right, ret, dep + 1); //dep + 1表示深度 + 1
}
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
int dep = 0;
DFS(root,result,dep);
return result;
}
};
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> qu; //二叉树类型队列
vector<vector<int>> result; //存放遍历结点结果集
if(root != NULL) qu.push(root); //存入根结点
//开始队列操作
while(!qu.empty()) //直至队列为空
{
int sz = qu.size(); //获取当前队列的大小
vector<int> vec; //装载每一层的树层结点
for(int i = 0;i < sz; ++i)
{
TreeNode* node = qu.front(); //获取当前队列的队头元素
qu.pop(); //出队队头元素
vec.push_back(node->val);
//子树的入队
if(node->left) qu.push(node->left); //左孩子入队
if(node->right) qu.push(node->right); //右孩子入队
}
result.push_back(vec); //每一层结点放入结果集
}
return result;
}
};
原题传送门
思路分析
return false;
NULL
为止,表示从根节点到这个结点的所有结点值均相同。但是呢并不是左子树相同就行,还要去递归其右子树,只有左右子树均相同后,才表示所有的结点值相同,那么这就是一棵【单值二叉树】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);
}
isUnivalTree(root->left) && isUnivalTree(root->right)
使用到的是一个【逻辑与】的操作符,它的运算规则是操作符两边的表示式均为真那么整个结果才为真,若是有一个为假则整个表达式为假 —— 如果不理解可以看看我的操作符汇总大全原题传送门
本题对于后面的两题都很有帮助,因此要重点理解
思路分析
bool isSameTree(struct TreeNode* p, struct TreeNode* q)
代码详解
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 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->right) && isSameTree(p->right,q->left);
}
bool isSymmetric(struct TreeNode* root){
if(root == NULL)
return true;
return isSameTree(root->left,root->right);
}
原题传送门
因为刚才讲到了镜像二叉树,和本次的翻转二叉树很像,因此将本题也纳入讲解
题目描述
思路分析
swap(root->left,root->right);
invertTree(root->left);
invertTree(root->right);
swap(node->left,node->right);
代码详解
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root == NULL) return root;
swap(root->left,root->right);
invertTree(root->left);
invertTree(root->right);
return root;
}
};
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
queue<TreeNode*> qu;
if(root == NULL) return NULL;
qu.push(root);
while(!qu.empty())
{
int sz = qu.size();
for(int i = 0;i < sz; ++i)
{
TreeNode* node = qu.front();
qu.pop();
//此时node已经取到队头结点
swap(node->left,node->right);
//继续遍历左右孩子
if(node->left) qu.push(node->left);
if(node->right) qu.push(node->right);
}
}
return root;
}
};
原题传送门
上一题是一段小插曲,本题也需要使用到No.100的代码,也就是【相同的树】,不过本题稍微难理解一些,所以我会讲得详细一点
题目描述
思路分析
代码详解
//全比一下
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){
//提示中给出子树不可能为空,因此root为空就表示不存在子树
if(root == NULL)
return false;
//首先比较两棵树的根是否相同
if(isSameTree(root,subRoot))
return true;
//根不同则去递归root的左右子树继续比较
return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
//使用逻辑或,左子树找到了就无需在往右子树寻找
}
递归算法图分解
本题还有一些坑,而且代码可能晦涩难懂一些,因为我画一下递归展开图给你看看,你也可以尝试自己画画
isSameTree()
去比较了两个根节点与其左右孩子结点是否相同,很明显③和④已经不相同的,所以root便向其左子树递归,此时再去比较就可以发现在root主树中找到了这棵子树,所以【return true】原题传送门
本题是从【牛客】摘录过来的,也是比较经典的一道题目
对于牛客来说,和力扣不一样的地方在于它是IO型OJ,而力扣则是接口型OJ,对于IO型的OJ呢我们需要和在VS里面一样,头文件、main函数均需要自己添加,也就是输入输出都由自己来完成;但是对于接口型OJ你只需实现题目给出的这一部分功能即可,但是相对许多公司的面试来说,大多都会采用【牛客】这个平台来进行面试,所以大家在熟悉了力扣之后,对于牛客也要多熟悉熟悉
思路分析
先序遍历构造树
看了视频讲解之后,相信你对先序遍历如何去进行构造应该有了一定的了解,接下去我们将这个思路转化为代码的形式
BTNode* reBuildTree(char* str, int *pi)
//若有值,则创建结点存放
BTNode* node = (BTNode *)malloc(sizeof(BTNode));
node->data = str[(*pi)++]; //赋值后指向字符序列下一个字符
//递归当前结点的左右子树
node->lchild = reBuildTree(str,pi);
node->rchild = reBuildTree(str,pi);
代码详解
#include
#include
typedef struct BinaryTreeNode{
char data;
struct BinaryTreeNode* lchild;
struct BinaryTreeNode* rchild;
}BTNode;
//指针接收地址
BTNode* reBuildTree(char* str, int *pi)
{
if(str[*pi] == '#')
{ //#表示空树
(*pi)++;
return NULL;
}
//若有值,则创建结点存放
BTNode* node = (BTNode *)malloc(sizeof(BTNode));
node->data = str[(*pi)++]; //赋值后指向字符序列下一个字符
//递归当前结点的左右子树
node->lchild = reBuildTree(str,pi);
node->rchild = reBuildTree(str,pi);
return node;
}
void InOrder(BTNode* root)
{
if(root == NULL)
return;
InOrder(root->lchild);
printf("%c ",root->data);
InOrder(root->rchild);
}
int main() {
int i = 0;
char str[100];
scanf("%s",str);
BTNode* reTree = reBuildTree(str,&i);
InOrder(reTree);
return 0;
}
持续更新中,不断汇总二叉树面试题
DFS
class Solution {
public:
void order(TreeNode* cur, vector<vector<int>>& result, int depth)
{
if (cur == nullptr) return;
if (result.size() == depth) result.push_back(vector<int>());
result[depth].push_back(cur->val);
order(cur->left, result, depth + 1);
order(cur->right, result, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;
order(root, result, depth);
return result;
}
};
BFS
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
vector<vector<int>> result;
while (!que.empty()) {
int size = que.size();
vector<int> vec;
// 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
result.push_back(vec);
}
return result;
}
};