二叉树OJ
1.单值二叉树
题目描述
解题思路:
- 针对上面的二叉树的题目,我们可以使用递归的方式对其进行求解
- 一颗树是单值的,当且仅当根节点的子节点所在的子树也是单值的,同时根节点的值与子节点的值相同。
- 我们可以使用递归实现这个判断的过程。left 表示当前节点的左孩子是正确的,也就是说:左孩子所在的子树是单值的,并且当前节点的值等于左孩子的值。right 对当前节点的右孩子表示同样的事情。递归处理之后,当根节点的这两种属性都为真的时候,我们就可以判定这颗二叉树是单值的
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isUnivalTree(TreeNode* root) {
if (root == NULL)
{
return true;
}
if (root->left != NULL && root->val != root->left->val) {
return false;
}
if(root->right != NULL && root->val != root->right->val) {
return false;
}
return isUnivalTree(root->left) && isUnivalTree(root->right);
}
};
复杂度分析
时间复杂度:O(N),其中 N 是给定树中节点的数量。
空间复杂度:O(H),其中 H 是给定树的高度。
解题思路Ⅱ:
- 同样针对这道题,我们还可以使用递归的方式对问题进行求解
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isUnivalTree(TreeNode* root) {
stack<TreeNode*> s;
if(root == NULL)
return true;
s.push(root);
int val = root->val; //设置初始值为root的值
TreeNode* node;
while(!s.empty())
{
node = s.top();
s.pop();
if(val != node->val)
return false; //发现不一致的现象,直接返回false
if(node->left != NULL)
s.push(node->left);
if(node->right != NULL)
s.push(node->right);
}
return true; //所有节点遍历完都一致,那么返回true
}
};
2.二叉树的最大深度
题目描述
解题思路:
- 如果我们知道了左子树和右子树的最大深度 ll 和 rr,那么该二叉树的最大深度即为max(l,r)+1,而左子树和右子树的最大深度又可以以同样的方式进行计算。因此我们可以用「深度优先搜索」的方法来计算二叉树的最大深度。具体而言,在计算当前二叉树的最大深度时,可以先递归计算出其左子树和右子树的最大深度,然后在 O(1)O(1) 时间内计算出当前二叉树的最大深度。递归在访问到空节点时退出
/**
* Definition for a binary tree node.
* 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:
int maxDepth(TreeNode* root)
{
if(root==nullptr)
return 0;
int left=maxDepth(root->left);
int right=maxDepth(root->right);
return left>right?left+1:right+1;
}
};
复杂度分析
- 时间复杂度:O(n),其中 n 为二叉树节点的个数。每个节点在递归中只被遍历一次。
- 空间复杂度:O(height),其中height 表示二叉树的高度。递归函数需要栈空间,而栈空间取决于递归的深度,因此空间复杂度等价于二叉树的高度。
3.翻转二叉树
题目描述
解题思路:
- 这是一道很经典的二叉树问题。显然,我们从根节点开始,递归地对树进行遍历,并从叶子结点先开始翻转。如果当前遍历到的节点root 的左右两棵子树都已经翻转,那么我们只需要交换两棵子树的位置,即可完成以 root 为根节点的整棵子树的翻转。
/**
* Definition for a binary tree node.
* 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:
TreeNode* invertTree(TreeNode* root)
{
if (root == NULL)
return root;
swap(root->left, root->right); // 中
invertTree(root->left); // 左
invertTree(root->right); // 右
return root;
}
};
复杂度分析
- 时间复杂度:O(N),其中 NN 为二叉树节点的数目。我们会遍历二叉树中的每一个节点,对每个节点而言,我们在常数时间内交换其两棵子树。
- 空间复杂度:O(N)。使用的空间由递归栈的深度决定,它等于当前节点在二叉树中的高度。在平均情况下,二叉树的高度与节点个数为对数关系,即 O(logN)。而在最坏情况下,树形成链状,空间复杂度为 O(N)
解法二:使用迭代的方法:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
stack<TreeNode*> st;
st.push(root);
while(!st.empty())
{
TreeNode* node = st.top();
st.pop();
swap(node->left, node->right);
if(node->right)
st.push(node->right);
if(node->left)
st.push(node->left);
}
return root;
}
};
4.相同的树
题目描述
解题思路:
- 如果两个二叉树都为空,则两个二叉树相同。如果两个二叉树中有且只有一个为空,则两个二叉树一定不相同。
- 如果两个二叉树都不为空,那么首先判断它们的根节点的值是否相同,若不相同则两个二叉树一定不同,若相同,再分别判断两个二叉树的左子树是否相同以及右子树是否相同。这是一个递归的过程,因此可以使用深度优先搜索,递归地判断两个二叉树是否相同
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q)
{
if(!p && !q) return true;
if(!p || !q) return false;
return (p->val == q->val)
&& (isSameTree(p->left,q->left)
&& isSameTree(p->right,q->right));
}
};
复杂度分析
- 时间复杂度:O(min(m,n)),其中 m 和 n 分别是两个二叉树的节点数。对两个二叉树同时进行深度优先搜索,只有当两个二叉树中的对应节点都不为空时才会访问到该节点,因此被访问到的节点数不会超过较小的二叉树的节点数。
- 空间复杂度:O(min(m,n)),其中 m 和 n 分别是两个二叉树的节点数。空间复杂度取决于递归调用的层数,递归调用的层数不会超过较小的二叉树的最大高度,最坏情况下,二叉树的高度等于节点数。
5.对称二叉树
题目描述
解题思路
- 我们可以实现这样一个递归函数,通过「同步移动」两个指针的方法来遍历这棵树,pp 指针和 qq 指针一开始都指向这棵树的根,随后 p 右移时,q左移,p 左移时,q 右移。每次检查当前 p 和 q 节点的值是否相等,如果相等再判断左右子树是否对称。
复杂度分析
- 假设树上一共 nn 个节点。
- 时间复杂度:这里遍历了这棵树,渐进时间复杂度为 O(n)
- 空间复杂度:这里的空间复杂度和递归使用的栈空间有关,这里递归层数不超过 n,故渐进空间复杂度为 O(n)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isSymmetric(TreeNode* root)
{
if(root == NULL) return true;
return isMirror(root->left,root->right);
}
bool isMirror(TreeNode *p,TreeNode *q) //递归函数
{
if(!p && !q) return true; //如果p,q均为NULL
if(!p || !q) return false; //p,q只有一者为NULL
return (p->val==q->val) && isMirror(p->left,q->right) && isMirror(p->right,q->left);
}
};
方法二:迭代
- 「方法一」中我们用递归的方法实现了对称性的判断,那么如何用迭代的方法实现呢?首先我们引入一个队列,这是把递归程序改写成迭代程序的常用方法。初始化时我们把根节点入队两次。每次提取两个结点并比较它们的值(队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像),然后将两个结点的左右子结点按相反的顺序插入队列中。当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。
class Solution {
public:
bool check(TreeNode *u, TreeNode *v) {
queue <TreeNode*> q;
q.push(u); q.push(v);
while (!q.empty()) {
u = q.front(); q.pop();
v = q.front(); q.pop();
if (!u && !v) continue;
if ((!u || !v) || (u->val != v->val)) return false;
q.push(u->left);
q.push(v->right);
q.push(u->right);
q.push(v->left);
}
return true;
}
bool isSymmetric(TreeNode* root) {
return check(root, root);
}
};
6.二叉树的前序遍历
题目描述
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct Tree
Node *right;
* };
*/
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
typedef struct TreeNode Node;
int GetNodeCount(Node *root)
{
if(NULL==root)
return 0;
return GetNodeCount(root->left)+GetNodeCount(root->right)+1;
}
void _PreOrder(Node *root,int* pRet,int* index) //用于保存
{
if(root)
{
pRet[*index]=root->val;
(*index)++;
_PreOrder(root->left,pRet,index);
_PreOrder(root->right,pRet,index);
}
}
int* preorderTraversal(struct TreeNode* root, int* returnSize)
{
*returnSize=0;
int *pRet=(int*)malloc(sizeof(int)*GetNodeCount(root));
_PreOrder(root,pRet,returnSize);
return pRet;
}
方法一:递归
- 首先我们需要了解什么是二叉树的前序遍历:按照访问根节点——左子树——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。
- 定义 preorder(root) 表示当前遍历到 root 节点的答案。按照定义,我们只要首先将 root 节点的值加入答案,然后递归调用 preorder(root.left) 来遍历 root 节点的左子树,最后递归调用 preorder(root.right) 来遍历 root 节点的右子树即可,递归终止的条件为碰到空节点。
/**
* Definition for a binary tree node.
* 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
{
private:
vector<int> Pre;
public:
vector<int> preorderTraversal(TreeNode* root)
{
if(root==nullptr)
return Pre;
Pre.push_back(root->val);
preorderTraversal(root->left);
preorderTraversal(root->right);
return Pre;
}
};
复杂度分析
- 时间复杂度:O(n),其中 n 是二叉树的节点数。每一个节点恰好被遍历一次。
- 空间复杂度:O(n),为递归过程中栈的开销,平均情况下为O(logn),最坏情况下树呈现链状,为 O(n)
方法二:迭代
- 我们也可以用迭代的方式实现方法一的递归函数,两种方式是等价的,区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同,具体可以参考下面的代码。
/**
* Definition for a binary tree node.
* 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:
vector<int> preorderTraversal(TreeNode* root)
{
stack<TreeNode*> st;
vector<int> result;
st.push(root);
while(!st.empty())
{
TreeNode *node=st.top();
st.pop();
if(node!=NULL)
{
result.push_back(node->val);
}
else
{
continue;
}
st.push(node->right);
st.push(node->left);
}
return result;
}
};
7.平衡二叉树
- 因为从上到下计算树的高度的话,会有很多的重复计算,那么我们会想到把从上到下计算树的高度改变成为从下到上计算树的高度,那么修改之后的代码如下所示:
- 但是,其实上面的那个代码也不是完全正确的,因为按照上面的那种代码的思路来思考,实践发现结果给我们返回左侧的树是一颗平衡二叉树,但是根据平衡二叉树的概念可以找到,左侧的那棵树其实并不是一颗平衡二叉树,所以说,上面的代码其实还是有问题的。
- 上面的那个代码也是有优点的,至少规避了第一种方法重复计算所带来的复杂的问题
- 要对上面的代码进行修改,就是要根据平衡二叉树的概念,增加一个对左右子树高度的判断就可以了
题目描述
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
int maxDepth(struct TreeNode* root)
{
int leftHeight = 0;
int rightHeight = 0;
if(NULL==root)
return 0;
leftHeight=maxDepth(root->left);
rightHeight=maxDepth(root->right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool isBalanced(struct TreeNode* root)
{
int leftHeight = 0;
int rightHeight = 0;
if(NULL == root)
return 1; //空树也是平衡树
leftHeight = maxDepth(root->left);
rightHeight = maxDepth(root->right);
return abs(rightHeight - leftHeight) < 2 && isBalanced(root->left) && isBalanced(root->right);
}
8.二叉树的遍历
题目描述
#include<stdlib.h>
#include<stdio.h>
#include<assert.h>
#include<string.h>
typedef struct TreeNode
{
struct TreeNode *left;
struct TreeNode *right;
char ch;
}TreeNode;
//创建结点
TreeNode *BuyTreeNode(char ch)
{
TreeNode *newnode=(TreeNode*)malloc(sizeof(TreeNode));
if(NULL==newnode)
return NULL;
newnode->left=NULL;
newnode->right=NULL;
newnode->ch=ch;
return newnode;
}
TreeNode *CreateBinTree(char szTree[],int size,int *index,char invalid)
{
TreeNode *root=NULL;
//创建根节点
if(*index<size&&szTree[*index]!=invalid)
{
root=BuyTreeNode(szTree[*index]);
//创建根节点的左子树
++(*index);
root->left=CreateBinTree(szTree,size,index,invalid);
//创建根节点的右子树
++(*index);
root->right=CreateBinTree(szTree,size,index,invalid);
}
return root;
}
void InOrder(TreeNode *root)
{
if(root)
{
InOrder(root->left);
printf("%c ",root->ch);
InOrder(root->right);
}
}
int main()
{
char szTree[100]={0};
int size=0;
int index=0;
while(EOF != scanf("%s",&szTree))
{
scanf("%s",&szTree);
size=strlen(szTree);
TreeNode *root=CreateBinTree(szTree,size,&index,'#');
//进行中序遍历
InOrder(root);
printf("\n");
}
return 0;
}
9.另一个树的子树
题目描述
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
//可以先考虑特殊情况
//如果两颗树都是空的,那么两棵树肯定是相同的
if(NULL==q&&NULL==p)
return true;
//一棵树为空,那肯定不相同
if(p==NULL||q==NULL)
return false;
return p->val==q->val&&isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}
bool isSubtree(struct TreeNode* s, struct TreeNode* t)
{
if(NULL==s&&NULL==t)
return true;
if(NULL==s||NULL==t)
return false;
if(s->val==t->val&&isSameTree(s,t)) //根一样
return true;
return isSubtree(s->left,t)||isSubtree(s->right,t);
}