标签:
Lowest common ancestor problem of binary tree 分析:
树是二叉搜索树 binary search tree BST
TreeNode *LCAofBST(TreeNode* root, TreeNode *p, TreeNode *q) { if(root == NULL | p == NULL | q == NULL) return NULL; //异常 if(root->val > p->val && root->val > q->val) return LCAofBST(root->left,p,q); //p,q都比root小,则检测左子数 else if(root->val < p->val && root->val < q->val) return LCAofBST(root->right, p, q); //p,q都比root大,则检测右子树 else return root; //情况1 root值在p,q之间,或 等于p,q的其中一个 //即:p->val < root->val <q->val | root->val == p->val || root->val == q->val } //算法默认可以默认p,q都是树root的节点,且p->val < q->val; 判断p,q 是树的节点:可以通过一个check函数自己实现 保证p < q : 加一层递归 if(p->val > q->val) return LCAofBST(root,q,p); 当然在上面的函数中不需要。因为else 包含了所有特殊情况
2. 树不是二叉树,当存在指向父节点的指针
struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode* parent; //指向root TreeNode(int x):val(x),left(NULL),right(NULL),parent(NULL){} };
找到p,q节点,把问题转换成判断链表公共节点的问题
对于带parent的情况,自然希望复杂度比上面的更低,算法更高效。很直观的解法是:利用O(n)的空间标记当前访问过的节点。从一个节点出发访问到root,再从另一个出发访问到root,遇到的第一个访问过的node即为想要的ancestor。
但如果要求O(1)的空间呢:我的思路是可以用树本身的信息来存储这个信息:访问过节点的parent标记为NULL,也能起到相同效果。但是这样的话会修改原来tree的结构,虽然parent可以通过树的其他信息来恢复,但是这要么需要O(n)的时间(修复整棵树),要么需要额外的空间(保存原来的parent映射)
最后是Leetcode官网的答案:
有parent节点的情况下,最自然的思路是两边都往上找,如果遇到就是要的结果。但是如果两个节点在不同的level,那么他们速度相同的情况下不会相遇。如果能知道他们之间的level差别,则可以不同时出发同时相遇。而这一差别就是他们到root距离的区别。所以只要找出他们的level,然后按level差不同时出发,就能在第一个common ancestor 相遇(即类似通过链表长度计算);
3. 普通的二叉树
3.1 根据思路二: 首先获取root到p,q的path,然后通过path的查找最远公共节点
void getPath(TreeNode* root, TreeNode* p,vector<TreeNode*>p,vector<TreeNode*>&path) { //getPath的方法很多,如前序遍历,此处使用 DFS 加 剪枝 的方法 vector<TreeNode *> path; if(root == NULL) return; if(root == p){ //find path = p; return; }; p.push_back(root); getPath(root->left,p,path); getPath(root->right,q,path); p.pop_back(root); } //简单的思路,没有运行
3.2 使用递归的思路进行判断
O(n^2)的解法很直观:top-down地看每个root是否cover both nodes
然后是O(n)的解法:bottom-up地找cover了哪些node。对于树来说,bottom-up这个概念不是很直观:访问都是从root开始向leaf走,怎么会bottom-up。而实际上,这里的bottom-up是指:在访问到leaf后结果按照bottom-up的方式向上传递。之前top-down approach是每次都检查两边的子树,然后进入一棵,再检查其子树,重复的部分在于:子树可能被多次check。而通过bottom-up的方式,信息流只需要一次就能到位。
Node *LCA(Node *root, Node *p, Node *q) { //一定搞清楚 递归结束条件 和 递归处理过程,这是个很妙的递归 if (!root) return NULL; if (root == p || root == q) return root; Node *L = LCA(root->left, p, q); Node *R = LCA(root->right, p, q); if (L && R) return root; // if p and q are on both sides,找到公共节点 return L ? L : R; // either one of p,q is on one side OR p,q is not in L&R subtrees }
算法优化,如果有多次重复问题的情况下,找出每次重复之间的重复子问题关系,寻找合适的信息流向让计算更快捷
树的recursive算法不一定要先判断当前节点再判断子树,如果子树的信息对于当前节点有用,则可以颠倒顺序变成bottom-up的方式
树的bottom-up方式和top-down方式的主要差别就在于:先处理当前节点还是先处理子树
recursion时,返回的值不一定要是最终的是否判断。也可以返回其他包含信息量更多的内容。
标签:
原文地址:http://my.oschina.net/u/573270/blog/488567