8.对称二叉树 101
判断二叉树是否对称,就要比较根节点的左右子树是否相等,那么要比较的就不是左右节点,而是左右子树的内侧和外侧元素是否分别相等。
遍历顺序只能是后序遍历,因为要通过左右孩子(即内侧和外侧节点)的比较结果,来判断这两棵子树是否对称。
使用递归。
第一步:确定函数参数及返回值。因为要判断根节点的左右子树是否可翻转,所以传入一个左节点和一个右节点;判断是否对称,所以返回值类型为Bool。
第二步:确定递归的终止条件。当左节点为空而右节点不为空时,FALSE;当左节点不为空而右节点为空时,FALSE;当左右节点均为空时,也算对称,TRUE;当左右节点均不为空但数值不相等时,FALSE。
第三步:确定单层递归逻辑。在左右节点均不为空且数值相等时,就要向下一层比较了。比较下一层时,就是比较两个中间节点的外侧和内侧是否分别相等,即左节点的左孩子和右节点的右孩子是否相等、左节点的右孩子和右节点的左孩子是否相等。二者均相等的话,说明这两棵子树是对称的,将结果返回上一层。
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right)
{
//先处理有节点为空的情况
if(left!=NULL && right==NULL) return false;
else if(left==NULL && right !=NULL) return false;
else if(left==NULL && right==NULL) return true;
//节点均不为空但数值不相等
else if(left->val != right->val) return false;
//节点均不为空且数值相等,就向下一层判断
//此时开始单层递归
bool outside=compare(left->left,right->right); //外侧:左节点的左孩子和右节点的右孩子
bool inside=compare(left->right,right->left); //内侧:左节点的右孩子和右节点的左孩子
bool isSame=outside && inside; //二者都相等,说明左右子树对称
return isSame;
}
bool isSymmetric(TreeNode* root) {
if(root==NULL) return true;
return compare(root->left,root->right);
}
};
100.相同的树
class Solution {
public:
bool compare(TreeNode* p, TreeNode* q)
{
//传入的是两棵二叉树的根节点
//先确定终止条件
if(p==NULL && q!=NULL) return false;
else if (p!=NULL && q==NULL) return false;
else if (p==NULL && q==NULL) return true;
else if (p->val != q->val) return false;
//单层递归逻辑
bool compareLeft=compare(p->left,q->left);
bool compareRight=compare(p->right,q->right);
bool isSame=compareLeft && compareRight;
return isSame;
}
bool isSameTree(TreeNode* p, TreeNode* q) {
return compare(p,q);
}
};
572.另一棵树的子树(没完全掌握)
判断一棵树是否是另一棵树的子树,有以下五种情况
(1)这两棵树本身相等;
(2)是这棵树的左子树;
(3)是这棵树的右子树;
(4)是左子树的子树;
(5)是右子树的子树。
前三种情况直接调用二叉树比较函数即可,但后面两种的根节点发生了变化,要重新调用传入根节点的函数,传入新的根节点。以原根节点的子树的根节点为新的根节点。
class Solution {
public:
//构造比较两个树是否相等的函数
bool compare(TreeNode* p, TreeNode* q) //传入两个树的根节点
{
if(p!=NULL && q==NULL) return false;
else if(p==NULL && q!=NULL) return false;
else if(p==NULL && q==NULL) return true;
else if(p->val!=q->val) return false;
bool compareLeft=compare(p->left,q->left);
bool compareRight=compare(p->right,q->right);
bool isSame=compareLeft && compareRight;
return isSame;
}
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if(root==NULL) return false; //把a二叉树都遍历完了还没匹配上,说明不成立
//b树是a树的子树的几种情况
return compare(root,subRoot) //1.ab这两棵树本身是否相等
|| compare(root->left,subRoot) //2.b这棵树是a的左子树?
|| compare(root->right,subRoot) //3.b这棵树是a的右子树?
|| isSubtree(root->left,subRoot) //4.不局限于a左子树本身,b可能还是左子树的子树,因此要重新调用函数,把新的根节点传入
|| isSubtree(root->right,subRoot); //5.b可能是a右子树的子树,所以传入新的根节点
}
};
9.二叉树的最大深度 104
迭代法,在二叉树的层序遍历里出现过
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==NULL) return 0;
int depth=0;
queue<TreeNode*> que;
que.push(root);
while(!que.empty())
{
int size=que.size();
depth++;
for(int i=0; i<size; i++)
{
TreeNode* node=que.front();
que.pop();
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
}
return depth;
}
};
递归法(后序遍历)
按理来说,求二叉树的深度,应该用前序遍历,这里为什么用后序?因为代码的逻辑实际上是求根节点的高度,根节点的高度就是二叉树的最大深度,所以用的是求高度的后序遍历。
class Solution {
public:
//先写一个求深度的函数
int getDepth(TreeNode* node) //第一步:确定函数参数及返回值:传入一个二叉树节点,求的是深度,所以返回值类型为int
{
if(node==NULL) return 0; //终止条件
//单层递归逻辑
int leftDepth=getDepth(node->left); //左子树深度
int rightDepth=getDepth(node->right); //右子树深度
int depth=1+max(leftDepth,rightDepth); //中间,处理逻辑,左右子树的最大深度+1即为二叉树的最大深度
return depth;
}
int maxDepth(TreeNode* root) {
return getDepth(root);
}
};
559.N叉树的最大深度
递归法(一层for循环,用时8ms)
class Solution {
public:
int maxDepth(Node* root) {
if(root==NULL) return 0;
int depth=0;
for(int i=0; i<root->children.size(); i++)
{
depth=max(depth,maxDepth(root->children[i]));
}
return depth+1;
}
};
迭代法(while+两层for循环,用时16ms)
class Solution {
public:
int maxDepth(Node* root) {
if(root==NULL) return 0;
int depth=0;
queue<Node*> que;
que.push(root);
while(!que.empty())
{
int size=que.size();
depth++;
for(int i=0; i<size; i++)
{
Node* node=que.front();
que.pop();
for(Node* child : node->children) //冒号后是要遍历的集合,冒号前是实例化一个集合中包含的元素
{
if(child) que.push(child);
}
}
}
return depth;
}
};
10.二叉树的最小深度 111
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
递归法:
求最大深度可以直接求两个子树的最大深度再+1,但求最小深度不能直接按照两个子树的最小深度+1来算,因为可能某一侧的子树为空,这时最小深度应该是不为空的子树的最小深度+1,所以要先判断一下。求最大深度不需要这一步。
class Solution {
public:
//求最小深度的函数
int getDepth(TreeNode* node)
{
if(node==NULL) return 0;
int leftDepth=getDepth(node->left);
int rightDepth=getDepth(node->right);
//左子树为空而右子树不为空,则最小深度按照右子树来算
if(node->left==NULL && node->right!=NULL)
{
return 1+rightDepth;
}
//左子树不为空而右子树为空,则最小深度按照左子树来算
if(node->left!=NULL && node->right==NULL)
{
return 1+leftDepth;
}
//左右子树都为空或都不为空,按照最小值计算即可
int result=1+min(leftDepth,rightDepth);
return result;
}
int minDepth(TreeNode* root) {
return getDepth(root);
}
};
迭代法:
过程和求最大深度一样,遍历的层数就是深度,只不过是碰到左右孩子均为空的时候要直接返回。
class Solution {
public:
int minDepth(TreeNode* root) {
if(root==NULL) return 0;
int depth=0;
queue<TreeNode*> que;
que.push(root);
while(!que.empty())
{
int size=que.size();
depth++;
for(int i=0; i<size; i++)
{
TreeNode* node=que.front();
que.pop();
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
if(!node->left && !node->right) return depth;
}
}
return depth;
}
};
11.完全二叉树的节点个数 222
方法一:按照普通二叉树来求
递归法:后序遍历
class Solution {
public:
//先写一个求节点个数的函数
int getNumber(TreeNode* node)
{
if(node==NULL) return 0;
int leftNum=getNumber(node->left); //左:求左子树节点数量
int rightNum=getNumber(node->right); //右:求右子树节点数量
int treeNum=leftNum+rightNum+1; //中:处理逻辑,总节点数量=左+右+1
return treeNum;
}
int countNodes(TreeNode* root) {
return getNumber(root);
}
};
迭代法:和层序遍历类似的流程,只是需要统计一下遍历过的节点数量
class Solution {
public:
int countNodes(TreeNode* root) {
if(root==NULL) return 0;
int count=0;
queue<TreeNode*> que;
que.push(root);
while(!que.empty())
{
int size=que.size();
for(int i=0; i<size; i++)
{
TreeNode* node=que.front();
que.pop();
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
count++;
}
}
return count;
}
};
方法二:按照完全二叉树来求(没完全掌握)
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。
对于情况二,本身不是满二叉树,则分别递归左孩子和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树(直到叶子节点),然后依然可以按照情况1来计算。
后序遍历。
class Solution {
public:
int countNodes(TreeNode* root) {
//终止条件:遇到什么情况需要返回
if(root==NULL) return 0;
TreeNode* left=root->left;
TreeNode* right=root->right;
int leftDepth=0, rightDepth=0; //为求指数方便,先初始化为0
while(left)
{
left=left->left; //一直向左往下走
leftDepth++; //这里感觉不对啊,实际上到第三层的时候才为1,算下来只有3个节点,实际应该有7个了
}
while(right)
{
right=right->right; //一直向右往下走
rightDepth++;
}
if(leftDepth==rightDepth) //左右子树深度相同,说明是一棵满二叉树,可以直接利用公式计算节点数量
{
return (2<<leftDepth)-1; //二进制,2左移2位等于2*2^2,左移3位等于2*2^3
//所以前面要先初始化深度为0,就是为了避免初始深度为1造成错误
}
//单层递归逻辑
//本身不是满二叉树,则分别递归左孩子和右孩子,最终一定可以递归到满二叉树上,然后按照公式来计算
int leftNum=countNodes(root->left); //左
int rightNum=countNodes(root->right); //右
int treeNum=leftNum+rightNum+1; //中
return treeNum;
}
};
12.平衡二叉树 110
递归法:(迭代法太复杂了)
二叉树节点的深度:从根节点到该节点的距离。因为是从上往下遍历,所以是前序遍历,先处理中间节点,然后再处理左右孩子。
二叉树节点的高度:从叶子节点到该节点的距离。因为是从下往上遍历,中间节点的高度需要参考左右孩子的高度,所以是先求左右孩子高度,再向上返回结果计算中间节点的高度。所以用后序遍历。
我们假设返回值为-1说明不是高度平衡二叉树。因为是求高度,所以使用后序遍历。
class Solution {
public:
//先写一个求高度的函数
int getHeight(TreeNode* node)
{
if(node==NULL) return 0; //递归的终止条件
//单层递归逻辑
//左:求左子树高度,若返回值为-1,说明左子树不是平衡二叉树,则该树必不是平衡二叉树,直接返回-1
int leftHeight=getHeight(node->left);
if(leftHeight==-1) return -1;
//右:求右子树高度,若返回值为-1,说明右子树不是平衡二叉树,则该树必不是平衡二叉树,直接返回-1
int rightHeight=getHeight(node->right);
if(rightHeight==-1) return -1;
//中:左右子树均为平衡二叉树,则将高度返回中间节点继续处理
int result;
if(abs(leftHeight-rightHeight)>1) //高度差大于1,则不是平衡二叉树
{
result=-1;
}
else
{
result=1+max(leftHeight,rightHeight); //是平衡二叉树,则求二叉树的高度
}
return result;
}
bool isBalanced(TreeNode* root) {
int result=getHeight(root);
return result==-1 ? false : true;
}
};
13.二叉树的所有路径 257
这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
递归之后要回溯,这样才能退回去重新开始新的一条路径。
1.递归函数参数及返回值
不需要返回值,所以是void。函数参数除了接收传入的节点之外,还需要一个vector<int>类型的数组来存放路径的节点(用vector是因为回溯的时候比较方便),还需要一个vector<string>类型的数组来存放符合要求的带->的最终结果。
2.终止条件
遍历到叶子节点就可以收集这一条路径了,但记得要把遍历到的叶子节点也加进去,不然是不完整的。刚开始路径节点是放进vector<int>类型的数组的,要处理一下转成符合要求的形式放进vector<string>类型的数组。
3.单层递归逻辑
左子树递归,然后回溯;右子树递归,然后回溯。必须要回溯,把需要弹出的节点都弹出去,才能进入下一条路径。
class Solution {
public:
//先写一个递归函数
void traversal(TreeNode* cur, vector<int>& path, vector<string>& result)
{
path.push_back(cur->val); //中,把中放在最前面是为了把叶子节点不要漏掉
//终止条件:到叶子节点了就可以结束遍历,路径就可以存进结果集了
if(cur->left==NULL && cur->right==NULL)
{
string sPath;
for(int i=0; i<path.size()-1; i++) //进行一下处理,把结果变成符合要求的字符串,注意循环是到size-1,不然会多一个->
{
sPath+=to_string(path[i]);
sPath+="->";
}
sPath+=to_string(path[path.size()-1]); //单独处理最后一个节点,即叶子节点
result.push_back(sPath); //收集一条路径
return; //在这就返回了,所以如果把“中”放在这一部分后面的话,会漏掉叶子节点没有放进去
}
//单层递归逻辑:先左后右
if(cur->left) //判断一下是否为空,如果是空的话就不进行下面的递归了
{
traversal(cur->left,path,result);
path.pop_back(); //每一次递归,都必须要跟一个回溯
}
if(cur->right)
{
traversal(cur->right,path,result);
path.pop_back();
}
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<int> path;
vector<string> result;
traversal(root,path,result);
return result;
}
};