[LeetCode Solution 1] 236.Lowest Common Ancestor of a Binary Tree
2015-11-19 22:33
459 查看
【题目概述】:
给定一棵二叉树T,以及二叉树中的两个节点p和q,试求p和q的最近公共祖先。而所谓p和q最近公共祖先,是指同时拥有p和q两个子孙的所有节点中,深度最大的节点(这里认为一个节点可以是自己的子孙)。
具体说来要完成如下的一个函数:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q);
其中root为指向所给树根节点的指针;p,q为指向待考察的两节点的指针;要求返回指向最近公共祖先的指针;
题目同时给出了如下的数据结构表示树中结点:
Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
【可行解题方法】:
方法一:基于先根遍历的深度搜索算法
这不是我想出来的方法,而是一种网上流传的方法,先呈上C++代码,稍后再分析:
对于每一层递归,L和R接受了更深一层的递归返回的值,将L和R结合起来看,不外乎有四类可能结果:
1. L = NULL,R = NULL; 表明以root->left为根节点的子树和以root->right为根节点的子树均不包含节点p和q。那么,当前层递归向上一层递归返回的值也是NULL。表明以root为根节点的子树不含p和q
2. L = NULL, R指向p; 表明以root->left为根节点的子树不包含节点p和q,但以root->right为根节点的子树仅包含节点p。那么,当前层递归向上一层递归返回的指针指向p。表明以root为根节点的子树仅含节点p
3. L指向p,R指向q;表明以root->left为根节点的子树仅包含节点p,而以root->right为根节点的子树仅包含节点q。此时可以断定,当前的root就是p和q的最近公共祖先。我们不妨记当前的root为ans(意为题目的答案)。那么,当前层递归向上一层递归返回的指针指向ans。表明以root为根节点的子树含节点p和q
4.L = NULL,R指向ans,表明以root->left为根节点的子树不包含节点p和q,但以root->right为根节点的子树同时包含p和q。那么,当前层递归向上一层递归返回的指针指向ans。表明以root为根节点的子树含节点p和q(注意此时的root不同于ans,是p和q的公共祖先,但已不是最近公共祖先了)。但没有关系, 指向ans的指针由于返回至上一层递归而得到了保留。这样,我们总可以在最上层函数的出口处得到指向ans的指针。
显然,由于p和q,L和R的等价性,可能的结果还有更多。比如:L = NULL, R指向q;L指向ans, R = NULL等。但不会超出如上所说的四种结果类型。为了更好的显示深搜回溯时的深层函数向浅层函数的传值情况,附示例如下:
View Code
分析时间复杂度:
层次遍历为节点编号时,共访问n次节点。而在寻找p,q的最近公共祖先时,比较坏的情况下,p,q均为叶子节点,而最近公共祖先为根节点,这样基于p的节点访问次数和基于q的节点访问次数也分别近似为n。总的时间复杂度为O(n)。
在LeetCode的测试平台上,方法一和二的耗时均为24ms。
【总结】
方法一:先根遍历,自顶向下,递归实现;方法二:层次遍历,自底向上,非递归实现。我认为方法一好一些,因为它具有更深邃的设计思想和更简洁,更简短,更清晰的代码。方法二以层次遍历的方式为树形结构进行线性排序,使反向寻找双亲成为可能。扩展一些,沿用方法二“为节点标号”的思想,如果我们以中根遍历的方式为节点标号,倘若以编号为关键字,则一棵普通树可以转化为一棵搜索二叉树,则可用此题的兄弟题LeetCode235."Lowest Common Ancestor of a Binary Search Tree"的方法解决。虽然我用中根遍历的方式失败了,但我仍认为这是一种可行的方法。
【附】如果你看到了我的这第一篇博客,如果我的阐释能对你有些启发,那么这些文字便有了意义。愿我们共同探索,共同进步!
给定一棵二叉树T,以及二叉树中的两个节点p和q,试求p和q的最近公共祖先。而所谓p和q最近公共祖先,是指同时拥有p和q两个子孙的所有节点中,深度最大的节点(这里认为一个节点可以是自己的子孙)。
具体说来要完成如下的一个函数:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q);
其中root为指向所给树根节点的指针;p,q为指向待考察的两节点的指针;要求返回指向最近公共祖先的指针;
题目同时给出了如下的数据结构表示树中结点:
Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
【可行解题方法】:
方法一:基于先根遍历的深度搜索算法
这不是我想出来的方法,而是一种网上流传的方法,先呈上C++代码,稍后再分析:
class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if (root == NULL) return NULL; //此时已无节点可供向下搜索,也同时表明此条路径上无p或q,返回空指针 if (root == p || root == q) return root; //如果root已为p或q,则不用再搜索以root为根节点的子树了,直接返回p或q的指针 TreeNode *L = lowestCommonAncestor(root->left, p, q); TreeNode *R = lowestCommonAncestor(root->right, p, q); if (L && R) return root; return L ? L : R; } };
对于每一层递归,L和R接受了更深一层的递归返回的值,将L和R结合起来看,不外乎有四类可能结果:
1. L = NULL,R = NULL; 表明以root->left为根节点的子树和以root->right为根节点的子树均不包含节点p和q。那么,当前层递归向上一层递归返回的值也是NULL。表明以root为根节点的子树不含p和q
2. L = NULL, R指向p; 表明以root->left为根节点的子树不包含节点p和q,但以root->right为根节点的子树仅包含节点p。那么,当前层递归向上一层递归返回的指针指向p。表明以root为根节点的子树仅含节点p
3. L指向p,R指向q;表明以root->left为根节点的子树仅包含节点p,而以root->right为根节点的子树仅包含节点q。此时可以断定,当前的root就是p和q的最近公共祖先。我们不妨记当前的root为ans(意为题目的答案)。那么,当前层递归向上一层递归返回的指针指向ans。表明以root为根节点的子树含节点p和q
4.L = NULL,R指向ans,表明以root->left为根节点的子树不包含节点p和q,但以root->right为根节点的子树同时包含p和q。那么,当前层递归向上一层递归返回的指针指向ans。表明以root为根节点的子树含节点p和q(注意此时的root不同于ans,是p和q的公共祖先,但已不是最近公共祖先了)。但没有关系, 指向ans的指针由于返回至上一层递归而得到了保留。这样,我们总可以在最上层函数的出口处得到指向ans的指针。
显然,由于p和q,L和R的等价性,可能的结果还有更多。比如:L = NULL, R指向q;L指向ans, R = NULL等。但不会超出如上所说的四种结果类型。为了更好的显示深搜回溯时的深层函数向浅层函数的传值情况,附示例如下:
class Solution { public: void markAllNode(TreeNode* root, TreeNode*p, TreeNode* q, TreeNode** queue, int* level, int& origin_p, int& origin_q, int& level_p, int& level_q) //进行层次遍历,为所有节点编号 { int front = 0,//指向队首元素下标 rear = 0,//指向队尾元素下标 current_level = -1;//指示当前待考察节点所在的层数,为了后面的循环处理方便,设为-1 queue[0] = root;//根节点事先入队 level[0] = 0;//处在第0层的第一个节点是编号为0的根节点(也是处在0层的唯一节点) if (p == root) { origin_p = 0; level_p = 0; } if (q == root) { origin_q = 0; level_q = 0; } while (front <= rear) { if (level[current_level + 1] == front) //发现待扩展节点为新一层的第一个节点 { current_level++;//更新当前考察节点所在层数 while (!queue[front]->left && !queue[front]->right) { front++; if (front > rear) { return; } } level[current_level + 1] = rear + 1;//预设下一层第一个节点编号 } if (!queue[front]->left && !queue[front]->right) { front++; continue; }//欲扩展的节点无孩子 if (queue[front]->left)//左孩子存在 { rear++; queue[rear] = queue[front]->left; if (p == queue[rear]) { origin_p = rear; level_p = current_level + 1; }//判断p是否指向该左孩子 if (q == queue[rear]) { origin_q = rear; level_q = current_level + 1; }//判断q是否指向该左孩子 } if (queue[front]->right)//右孩子存在 { rear++; queue[rear] = queue[front]->right; if (p == queue[rear]) { origin_p = rear; level_p = current_level + 1; } if (q == queue[rear]) { origin_q = rear; level_q = current_level + 1; } } front++; //front指向下一个待扩展的节点 } } int findLatestAncestor(TreeNode** queue, int* level, int descendant, int level_d) //寻找descendant所表示的节点的双亲,level_d表明节点所处层数 //返回双亲在层次遍历中的编号 { for (int i = level[level_d - 1]; i < level[level_d]; i++) //遍历上一层节点 { if (queue[i]->left == queue[descendant] || queue[i]->right == queue[descendant]) { return i; } } } TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { TreeNode* queue[10000]; //queue[i]存放对树进行层次遍历中,编号为i的节点的地址 int level[1000]; //level[i]存放树的第i层节点中,编号最小的结点,假设根结点所处层数为0 int origin_p, origin_q, level_p, level_q; //origin_p为p指针所指向的节点在层次遍历中的编号, //origin_q为q指针所指向的节点在层次遍历中的编号, //level_p为p指针所指向的节点在树中所处的层次, //level_q为q指针所指向的节点在树中所处的层次, markAllNode(root, p, q, queue, level, origin_p, origin_q, level_p, level_q); while (level_p > level_q) { origin_p = findLatestAncestor(queue, level, origin_p, level_p); level_p--; }//当origin_p所指向的节点在origin_q所指向的节点下方时,寻找origin_p的与origin_q处于同一层次的祖先 while (level_p < level_q) { origin_q = findLatestAncestor(queue, level, origin_q, level_q); level_q--; }//当origin_q所指向的节点在origin_p所指向的节点下方时,寻找origin_q的与origin_p处于同一层次的祖先 while (origin_p != origin_q) { origin_p = findLatestAncestor(queue, level, origin_p, level_p); level_p--; origin_q = findLatestAncestor(queue, level, origin_q, level_q); level_q--; }//每次循环均同时寻找origin_p和origin_q各自的双亲,并及时进行比较,判断是否找到了共同祖先 return queue[origin_p]; //返回指向p,q共同祖先的指针 } };
View Code
分析时间复杂度:
层次遍历为节点编号时,共访问n次节点。而在寻找p,q的最近公共祖先时,比较坏的情况下,p,q均为叶子节点,而最近公共祖先为根节点,这样基于p的节点访问次数和基于q的节点访问次数也分别近似为n。总的时间复杂度为O(n)。
在LeetCode的测试平台上,方法一和二的耗时均为24ms。
【总结】
方法一:先根遍历,自顶向下,递归实现;方法二:层次遍历,自底向上,非递归实现。我认为方法一好一些,因为它具有更深邃的设计思想和更简洁,更简短,更清晰的代码。方法二以层次遍历的方式为树形结构进行线性排序,使反向寻找双亲成为可能。扩展一些,沿用方法二“为节点标号”的思想,如果我们以中根遍历的方式为节点标号,倘若以编号为关键字,则一棵普通树可以转化为一棵搜索二叉树,则可用此题的兄弟题LeetCode235."Lowest Common Ancestor of a Binary Search Tree"的方法解决。虽然我用中根遍历的方式失败了,但我仍认为这是一种可行的方法。
【附】如果你看到了我的这第一篇博客,如果我的阐释能对你有些启发,那么这些文字便有了意义。愿我们共同探索,共同进步!
相关文章推荐
- 负边距在布局中的使用
- 使用ViewPager和Photoview实现图片左右滑动和放大缩小
- group 分组 查询 栏目下文章 的总数 having where区别
- win7的系统怎么把屏幕颜色设置成保护眼睛的绿色???
- Codeforces Round #281 (Div. 2) D. Vasya and Chess 博弈
- display:none和visibility:hidden的区别
- bootchartd简介
- java 序列化 serialVersionUID transient
- 【Leetcode】Palindrome Linked List
- AndroidStudio中方法注释模板快捷键的设置
- Android四大组件之Service
- Python __getattr__与__setattr__使用方法
- 第一个Web前端
- iOS页面间传值的方式(Delegate/NSNotification/Block/NSUserDefault/单例)
- Group Anagrams
- PAT-PAT (Advanced Level) Practise To Buy or Not to Buy(20) 【一星级】
- EventBroker
- Xcode7.0后,如何配置Pch
- 13.按比例显示图片、自定义属性、测量
- 百度api定位