二叉树题目总结
- 前言
- 一、整体思路
- 二、题目
- 1.[翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/)
- 2.[填充每个节点的下一个右侧节点指针](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/)
- 3.[二叉树展开为链表](https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/)
- 4.[最大二叉树](https://leetcode-cn.com/problems/maximum-binary-tree/)
- 5.[从前序与中序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)
- 6.[从中序与后序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/)
- 7.[寻找重复的子树](https://leetcode-cn.com/problems/find-duplicate-subtrees/)
- 8.[二叉搜索树中第K小的元素](https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/)
- 9.[把二叉搜索树转换为累加树](https://leetcode-cn.com/problems/convert-bst-to-greater-tree/)
- 10.[验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree/)
- 11.[二叉搜索树中的插入操作](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/)
- 12.[删除二叉搜索树中的节点](https://leetcode-cn.com/problems/delete-node-in-a-bst/)
- 13.[不同的二叉搜索树 II](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/)
- 14.[二叉搜索子树的最大键值和](https://leetcode-cn.com/problems/maximum-sum-bst-in-binary-tree/)
- 15.[完全二叉树的节点个数](https://leetcode-cn.com/problems/count-complete-tree-nodes/)
- 16.[二叉树的序列化与反序列化](https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/)
- 17.[二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)
- 总结
前言
最近做了一部分力扣上的二叉树题目,在这里总结一下思路规律。本刷题j记录基于labuladong的做题总结,特此致谢。
一、整体思路
首先,对二叉树类型的题目,要相信这类题目是有规律的,其基础就是树的三种遍历方式,更复杂的一些会需要添加参数,修改返回值等。二叉树基本上都可以用递归的思路解决,因而明确函数的定义,并坚信它成立,并遵照这个定义一步步实现是非常重要的。
二、题目
1.翻转二叉树
对这样的一道题,我们首先考虑如何定义函数。我们希望定义一个函数能够实现反转root为根的树,并返回root。
我们尝试用遍历的思想来完成这项任务。一种思路是前序遍历的思想,每次都从根节点开始处理,首先反转根节点的左右节点,然后迭代反转两棵子树。另一种是后序遍历的思想,先完成子树的反转,最后将根节点的左右子树反转。这里用中序遍历的思路就有些不合适,因为我们需要反转两棵子树,在中间反转会出现错误。
//前序遍历
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
reverse(root);
return root;
}
void reverse(TreeNode * root)
{
if(root==nullptr) return ;
TreeNode * temp;
temp=root->left;
root->left=root->right;
root->right=temp;
reverse(root->left);
reverse(root->right);
return ;
}
};
//后序遍历
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
reverse(root);
return root;
}
void reverse(TreeNode * root)//递归的关键是明确定义:反转以root为根的左右子树
{
if(root==nullptr) return ;
reverse(root->left);
reverse(root->right);
TreeNode * temp;
temp=root->left;
root->left=root->right;
root->right=temp;
return ;
}
};
通过这道题我们要先树立一个思想,就是只要函数定义好,就要相信这个函数能够实现我们想要的功能。
2.填充每个节点的下一个右侧节点指针
这道题的任务是将每一层的节点都都向右连接。乍一看,可能很难直接考虑如何用递归来做。对我们来说,最关键的是明确这个过程中,每一层的节点要做什么。
假如我们定义一个函数来完成填充以root为根的子树中每个节点的next指针,这样实现会导致左子树和右子树之间的部分的线无法连接。因而只有这一个参数是很难实现这一操作的。
我们可以考虑传入两个参数left,right,分别是左右两个子树的根节点,我们希望完成的工作是,能够完成子节点的相关连接。对于连线,我们可以考虑分成三部分,分别是左根的左子树与左根的右子树,左根的右子树和右根的左子树,以及右根的左子树和又跟的右子树。
class Solution {
public:
Node* connect(Node* root) {
if(root==NULL) return NULL;
wirenode(root->left,root->right);
return root;
}
void wirenode(Node * l,Node * r)//函数定义:连接两个根节点所有的同层节点(包括根节点在内)
{
if(l==NULL||r==NULL) return ;
l->next=r;
wirenode(l->left,l->right);
wirenode(r->left,r->right);
wirenode(l->right,r->left);
return ;
}
};
通过以上的方式,我们可以将节点之间的连接拆解为三个子任务,从而全覆盖。
3.二叉树展开为链表
同样。我们需要将问题分解为可以实现的问题,最重要的是明确根节点在函数中需要做什么。
这道题要求将树展开为右侧链表,且要求和先序遍历的顺序一致。因而我们可以想到,操作顺序应该是中、左、右这样的顺序。
首先第一步我们需要给函数下明确定义。所定义的函数的意义是将根为root的部分展开为链表,返回值是root节点。这样一来,操作过程就变为了,子树首先展开为链表,然后将左子树的对应链表拼接到右子树的对应链表尾部。通过这样的思路,我们能非常清晰地看到实现过程。最重要的是,对自己定义地函数概念足够相信清晰。当然,实现上,不管是返回根节点,还是void都可以。
/**
* 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:
void flatten(TreeNode* root) {
lat(root);
return ;
}
//两种不同的实现方式 这里返回根节点需要考虑的特殊情况更多一些
TreeNode * flatter(TreeNode * root)//
{
if(root==nullptr) return nullptr;
if(root->left==nullptr) { root->right=flatter(root->right); return root;}
if(root->right==nullptr)
{
root->right=flatter(root->left);
root->left=nullptr;
return root;
}
TreeNode * temp=flatter(root->left);
TreeNode * over=temp;
while(over->right!=nullptr) over=over->right;//左边的最下边节点
over->right=flatter(root->right);
root->right=temp;
root->left=nullptr;
return root;
}
void lat(TreeNode * root)
{
if(root==nullptr) return;
lat(root->left);
lat(root->right);
TreeNode * oril=root->left;
TreeNode * orir=root->right;
root->left=nullptr;
root->right=oril;
TreeNode * temp=root;
while(temp->right!=nullptr) temp=temp->right;
temp->right=orir;
return ;
}
};
4.最大二叉树
依照前面题目的经验,这道题同样可以实现。我们需要依照所给的数组来构造这样一个树,具体到每个节点而言,每次需要先找到数组的最大元素,然后依次递归到子数组即可。
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
TreeNode * root=new TreeNode();
maxtree(nums,0,nums.size()-1,root);
return root;
}
void maxtree(vector<int>& nums, int left,int right,TreeNode * root)
{
if(left==right)
{
root->val=nums[left];
return;
}
TreeNode * l=new TreeNode();
TreeNode * r=new TreeNode();
int idxmax=left;
for(int i=left;i<=right;i++) if(nums[i]>=nums[idxmax]) idxmax=i;
root->val=nums[idxmax];
if(idxmax==right)
{
root->left=l;
maxtree(nums,left,idxmax-1,l);
}
else if(idxmax==left)
{
root->right=r;
maxtree(nums,idxmax+1,right,r);
}
else
{
root->left=l;
root->right=r;
maxtree(nums,left,idxmax-1,l);
maxtree(nums,idxmax+1,right,r);
}
return ;
}
};
5.从前序与中序遍历序列构造二叉树
这道题的关键是理解前序遍历每个首节点是子节点。只需要在中序遍历串中找到对应子节点,然后分为左右两部分即可。
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return build(preorder,inorder,0,preorder.size()-1,0,inorder.size()-1);
}
TreeNode * build(vector<int> & preorder,vector<int> & inorder,int l1,int r1,int l2, int r2)
{
if(l1>r1) return nullptr;
TreeNode *root=new TreeNode();
root->val=preorder[l1];
int t=l2;
for(;t<=r2;t++) if(inorder[t]==preorder[l1]) break;
int lenl=t-l2,lenr=r2-t;
root->left=build(preorder,inorder,l1+1,l1+1+lenl-1,l2,t-1);
root->right=build(preorder,inorder,l1+lenl+1,r1,t+1,r2);
return root;
}
};
上述代码每次都要遍历一遍中序遍历数组,速度较慢,因为可以用hashtable。
class Solution {
public:
unordered_map<int,int> hashtable;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
for(int i=0;i<inorder.size();i++) hashtable.insert(pair<int,int>(inorder[i],i));
return build(preorder,inorder,0,preorder.size()-1,0,inorder.size()-1);
}
TreeNode * build(vector<int> & preorder,vector<int> & inorder,int l1,int r1,int l2, int r2)
{
if(l1>r1) return nullptr;
TreeNode *root=new TreeNode();
root->val=preorder[l1];
int t=hashtable.find(preorder[l1])->second;
int lenl=t-l2,lenr=r2-t;
root->left=build(preorder,inorder,l1+1,l1+1+lenl-1,l2,t-1);
root->right=build(preorder,inorder,l1+lenl+1,r1,t+1,r2);
return root;
}
};
6.从中序与后序遍历序列构造二叉树
思路和上一道题一致,不再赘述。
class Solution {
public:
unordered_map <int,int> hashtable;
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
for(int i=0;i<inorder.size();i++) hashtable.insert(pair<int,int>(inorder[i],i));
return build(inorder,postorder,0,inorder.size()-1,0,postorder.size()-1);
}
TreeNode * build(vector<int> & inorder,vector<int> & postorder,int l1,int r1,int l2, int r2)
{
if(l1>r1) return nullptr;
TreeNode *root=new TreeNode();
root->val=postorder[r2];
int t=hashtable.find(postorder[r2])->second;
root->left=build(inorder,postorder,l1,t-1,l2,r2-r1+t-1);
root->right=build(inorder,postorder,t+1,r1,r2-r1+t,r2-1);
return root;
}
};
7.寻找重复的子树
这道题关键是要能够记录下每个节点对应的树,然后判断是否出现过重复的。记录下每一颗子树的方法可以参照二叉树的序列化和反序列化来实现。
基于这个思想,我们可以实现如下代码:
#include <sstream>
class Solution {
public:
unordered_map<string,int> hashtable;
vector<TreeNode*> nodelist;
vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
string all=deverse(root);
return nodelist;
}
string deverse(TreeNode * root)
{
string res;
if(root==nullptr) return "#,";
res+=to_string(root->val);
res+=",";
res+=deverse(root->left);
res+=deverse(root->right);
if(hashtable.find(res)!=hashtable.end())
{
if(hashtable.find(res)->second==1)
nodelist.push_back(root);
hashtable.find(res)->second++;
}
else
{
//nodelist.push_back(root);
hashtable.insert(pair<string,int>(res,1));
}
return res;
}
};
以上代码里,hashtable第一个数存的是二叉树序列化的结果,第二个数存的是对应树出现的次数,但是这样的方法,在字符串匹配时会浪费较多的时间,因为这里考虑另一种放方法,将每个字符串映射为数字,从而大大缩短字符串比对所需的时间。
这里考虑先将每个未重复出现的字符串映射到不同的数字上,这样每次用不同的数字代表子串,从而提高速度。
#include <sstream>
class Solution {
public:
unordered_map<string,int> hashtable;
vector<TreeNode*> nodelist;
int idx=0;
vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
vector<int> numtree(10000,0);
int t=deverse(root,numtree);
return nodelist;
}
int deverse(TreeNode * root,vector<int> & numtree)//核心思想是遍历每个节点时,记录好本节点对应的子树,并与之前的相比较,没出现过就加入,出现过且曾出现过一次就输出
{
string res;
if(root==nullptr) return 0;
res+=to_string(root->val);
res+=",";
res+=to_string(deverse(root->left,numtree));
res+=to_string(deverse(root->right,numtree));
if(hashtable.find(res)!=hashtable.end())
{
if(numtree[hashtable.find(res)->second]==1)
nodelist.push_back(root);
numtree[hashtable.find(res)->second]++;
}
else
{
//如果没出现过的话
hashtable[res]=++idx;
numtree[idx]++;
// hashtable.insert(pair<string,int>(res,1));
}
return hashtable[res];
}
};
此时hashtable存储的是字符串和其唯一的数字标识,numtree存放的是对应序号所对应的字符串的出现次数。
8.二叉搜索树中第K小的元素
接下来的题目基本都是关于二叉搜索树的。首先我们要了解到,二叉搜索树的中序遍历对应的就是树值的升序遍历,因而只需要在前序遍历时记录下每个节点的位置即可。
class Solution {
public:
int res=0;
int count=0;
int kthSmallest(TreeNode* root, int k) {
midvisit(root,k);
return res;
}
void midvisit(TreeNode * root,int k)
{
if(root==nullptr) return;
midvisit(root->left,k);
count++;
if(count==k)
{
res=root->val; return;
}
midvisit(root->right,k);
}
};
9.把二叉搜索树转换为累加树
这道题里,对每个节点,我们要求出树中大于等于该节点值的和,由于二叉搜索树的中序遍历是升序的,因而其 反向的中序遍历就是降序的。我们可以想到,通过逆序遍历,记录下比每个节点值大的部分,然后重新赋值即可。
class Solution {
public:
int sum=0;
TreeNode* convertBST(TreeNode* root) {
construct(root);
return root;
}
void construct(TreeNode * root)//返回以该节点为根的子树整个数的节点总和,根节点赋值的是自己右边子树的总和值加自己的值
{
if(root==nullptr) return ;
construct(root->right);
sum+=root->val;
root->val=sum;
construct(root->left);
return ;
}
};
10.验证二叉搜索树
这道题一开始很容易陷入只判断根和左右子节点的误区中,但是事实上,对于左子树,我们需要保证其任意节点的值都小于根,对于右子树,我们需要保证任意节点的值都大于根。
这道题同样可以有不同的考虑方式,一种是前序遍历的方式,我们每次都需要将根节点的值传入到下一层中用来判断,另一种是将子树对应的值穿上根节点用来判断。具体简化到每个节点上,需要做的工作便是判断root的值是否在要求范围内,如果在所需范围内,则向下递归,或者向上递归。
这道题取巧的方法是取中序遍历,判断是否是升序的。
//先序遍历的方法
class Solution {
public:
vector<int> visit;
bool isValidBST(TreeNode* root) {
return judge(root,nullptr,nullptr);
}
bool judge(TreeNode * root,TreeNode * maxp, TreeNode * minp)
{
if(root==nullptr) return true;
if(minp!=nullptr&&root->val<=minp->val) return false;
if(maxp!=nullptr&&root->val>=maxp->val) return false;
return judge(root->left,root,minp)&&judge(root->right,maxp,root);
}
};
//后序遍历的方法
class Solution {
public:
struct rval
{
bool legal;
TreeNode* max;
TreeNode* min;
};
vector<int> visit;
bool isValidBST(TreeNode* root) {
return judge(root).legal;
}
rval judge(TreeNode * root)
{
rval res;
if(root==nullptr)
{
res.legal=true;
res.min=res.max=nullptr;
return res;
}
rval lj=judge(root->left);
rval rj=judge(root->right);
if(lj.legal&&rj.legal&&!((lj.max!=nullptr&&root->val<=lj.max->val)||(rj.min!=nullptr&&root->val>=rj.min->val)))
{
res.legal=true;
if(lj.min&&lj.min->val<=root->val) res.min=lj.min;
else res.min=root;
if(rj.max&&rj.max->val>=root->val) res.max=rj.max;
else res.max=root;
}
else res.legal=false;
return res;
}
};
//中序遍历较为简洁的实现方法
class Solution {
public:
TreeNode * pre=nullptr;
bool isValidBST(TreeNode* root) {
return isvalid(root);
}//函数依然定义为判断树是否为二叉搜索树,pre记录前一个节点
bool isvalid(TreeNode *root)
{
if(root==nullptr) return true;
if(!isvalid(root->left)) return false;
if(pre&&root->val<=pre->val) return false;
pre=root;
return isvalid(root->right);
}
};
11.二叉搜索树中的插入操作
这道题要主要考察如何在搜索树中正确位置插入一个数。直觉上,如果根节点值大于该数,则应该在左边加入该数,否则,应该在右边插入该数。具体实现上,如果函数返回值是完成相关操作后的根节点,就不需要记录上一层的节点,否则需要记录上一层的父节点,以方便进行插入。
//返回值为void,此时需要记录上一层节点
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(root==nullptr)
{
TreeNode * t=new TreeNode();
t->val=val;
return t;
}
insert(root,val,nullptr,0);
return root;
}
void insert(TreeNode * root,int value,TreeNode * upper,int dir)
{
if(root==nullptr)
{
TreeNode * t=new TreeNode();
t->val=value;
if(dir) upper->left=t;
else upper->right=t;
return ;
}
if(root->val>value) insert(root->left,value,root,1);
else insert(root->right,value,root,0);
return ;
}
};
//返回值为完成更新后的根节点
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
return insert(root,val);
}
TreeNode * insert(TreeNode * root,int target)
{
if(root==nullptr) return new TreeNode(target);
if(root->val>target) root->left=insert(root->left,target);
else root->right=insert(root->right,target);
return root;
}
};
12.删除二叉搜索树中的节点
考虑删除比插入更复杂一些。假如当前值比要删除的节点值大,应该在左子树删除,否则,应该在右子树删除。假如找到了要删除的节点,我们需要进一步考虑如何善后。
假如要删除的节点左右子树均为mull,则返回更新后的子树为null即可;假如左右子树只有一个为null,返回对应子树根节点。较为复杂的是当左右子树均存在时怎么办。
为了维持二叉搜索树的性质,我们需要找到左子树的最大值的节点,或者右子树的最小值的节点,并将其作为新的根节点,之后,需要删除该节点。
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
return delete_(root,key);
}
TreeNode * delete_(TreeNode * root,int key)
{
if(root==nullptr) return nullptr;
if(root->val==key)
{
if(root->left==nullptr) return root->right;
if(root->right==nullptr) return root->left;
else
{
TreeNode *t=root->right;
TreeNode * last=t;
while(t->left!=nullptr)
{
last=t;
t=t->left;
}
root->right=delete_(root->right,t->val);
t->left=root->left;
t->right=root->right;
return t;
}
}
else if(root->val>key) root->left=delete_(root->left,key);
else root->right=delete_(root->right,key);
return root;
}
};
13.不同的二叉搜索树 II
这道题本质上是数学题,我们考虑如何将其转化为规模更小的问题。
对一个数组,每个节点都可以作为根节点,从而递归的处理左右子树。这里注意相同长度的数组可以构成的种类数相同,因而可以预先记录下来,防止超时。
class Solution {
public:
int type[21];
int numTrees(int n) {
for(int i=0;i<21;i++) type[i]=-10;
return treenum(1,n);
}
int treenum(int left,int right)
{
if(right<=left) return 1;
int sum=0;
for(int i=left;i<=right;i++)
{
int temp1,temp2;
if(type[i-1-left+1]==-10)
{
temp1=treenum(left,i-1);
type[i-1-left+1]=temp1;
}
else temp1=type[i-1-left+1];
if(type[right-(i+1)+1]==-10)
{
temp2=treenum(i+1,right);
type[right-(i+1)+1]=temp2;
}
else temp2=type[right-(i+1)+1];
sum+=temp1*temp2;
}
return sum;
}
};
14.二叉搜索子树的最大键值和
这道题要找到二叉搜索子树的最大键值。从直觉上,我们只需要判断一下每个节点对应是否是二叉搜索树,然后返回最大的键值和即可。不过这些过程可以直接通过一个函数来实现,我们希望一个函数实现如下功能:1.判断以其为根的树是否为二叉搜索树2.返回其对应的最大值和最小值3.返回子树和。而这三个过程都可以在后序遍历里实现。同时要注意的是如果当前树不满足搜索树,则不需要继续计算,因为包含它的树都不是二叉搜索树。
class Solution {
public:
struct rval
{
bool ifbst;
TreeNode* max;
TreeNode* min;
int sum;
}returnval;
int sumres=INT_MIN;
int maxSumBST(TreeNode* root) {
judge(root);
if(sumres<=0) return 0;
return sumres;
}
rval judge(TreeNode * root)//明确函数功能:判断以root为节点的子树是否是bst,并向上传回该子树最大值和最小值,同时更新ressum
//之前错误的主要原因是,没有明确每一层函数要做的工作,不应该考虑下一层是否更新了结果
{
struct rval res;
if(root==nullptr)
{
res.ifbst=true;res.min=nullptr;res.max=nullptr;res.sum=0;
return res;
}
rval leftval=judge(root->left);
rval rightval=judge(root->right);
if(leftval.ifbst&&rightval.ifbst&&!((leftval.max!=nullptr&&root->val<=leftval.max->val)||(rightval.min!=nullptr&&root->val>=rightval.min->val)))
{
res.ifbst=true;
res.sum=root->val+leftval.sum+rightval.sum;
if(leftval.min&&leftval.min->val<=root->val) res.min=leftval.min;
else res.min=root;
if(rightval.max&&rightval.max->val>=root->val) res.max=rightval.max;
else res.max=root;
sumres=max(sumres,res.sum);
}
else {res.ifbst=false;}
return res;
}
};
15.完全二叉树的节点个数
这道题计算简化的方法是判断左右子树高度是否相同,如果相同直接用公式,否则返回左右子树和root的和。
class Solution {
public:
int countNodes(TreeNode* root) {
return count_(root);
}
int count_(TreeNode *root)
{
if(root==nullptr) return 0;
int lh=0,rh=0;
TreeNode * l=root->left, * r=root->right;
while(l!=nullptr) {lh++;l=l->left;}
while(r!=nullptr) {rh++;r=r->right;}
if(lh==rh) return pow(2,lh+1)-1;
else return 1+count_(root->left)+count_(root->right);
}
};
16.二叉树的序列化与反序列化
这道题需要依照自己的设计方式来序列化二叉树。比较巧妙的地方是,反序列化时依照序列化时的循序操作即可。
#include <string>
#include <sstream>
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
string res=strser(root);
return res;
}
string strser(TreeNode* root)
{
string Ser="";
if(root==nullptr)
{
Ser+="#,";
return Ser;
}
else Ser+=to_string(root->val)+',';
Ser+=serialize(root->left);
Ser+=serialize(root->right);
return Ser;
}
TreeNode* deserialize(string data) {
vector<string> liststr;
string str;
for(auto ch :data)
{
if(ch!=',')
{
str+=ch;
}
else
{
liststr.push_back(str);
str.clear();
}
}
if(!str.empty()) liststr.push_back(str);
TreeNode * res=dbylist(liststr);
return res;
}
TreeNode* dbylist(vector<string>& liststr)//函数的定义就是返回反序列化listatr的子树的根
{
if(liststr[0]=="#")
{
liststr.erase(liststr.begin());
return NULL;
}
string midval=liststr[0];
TreeNode * root=new TreeNode();
int v=stoi(midval);
root->val=v;
liststr.erase(liststr.begin());
root->left=dbylist(liststr);
root->right=dbylist(liststr);
return root;
}
};
17.二叉树的最近公共祖先
这道题关键在于如何设计函数的返回值。由于我们希望最后返回的是q、p两个节点的最近祖先,假如root为null。则p、q都不在其中,返回nullptr;假如root为p,假如q在其子树下,则公共祖先应为p,否则,假如q不在其子树下,我们返回p节点计科;假如root既不是p也不是q,那么我们需要分情况讨论:
1…如果两个子树返回都是空指针,说明p、q不在格树里,则应返回nullptr;
2.如果有一个返回不为空,说明起码一个分支里出现了其中一个节点,依照约定的定义,应该返回该节点;
3、如果均不为空,则说明一定是两个分支分别出现了p和q,则最近公共祖先为root。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
return commonanc(root,p,q);
}
TreeNode* commonanc(TreeNode * root,TreeNode *p,TreeNode *q)
{
// 希望函数能做几件事:1.pq都在里面,返回最近祖先2.p,q只有一个在里面,返回对应节点 3.pq都不在,返回null
if(root==NULL) return NULL;
if(root==p||root==q) return root;
TreeNode * l=commonanc(root->left,p,q);
TreeNode *r=commonanc(root->right,p,q);
if(!l&&!r) return NULL;
if(l&&r) return root;
return l==NULL?r:l;
}
};
总结
二叉树的相关题目,关键在于函数的定义,要充分理解三种遍历方式,并在这个基础上通过修改来实现所需的功能。且绝大多数情况下,依照题目要求的值来构造函数都有效,没有太多弯需要绕。