二叉树的最近公共节点问题

Lowest common ancestor problem of binary tree 分析:

  1.  树是二叉搜索树  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时,返回的值不一定要是最终的是否判断。也可以返回其他包含信息量更多的内容。


转载于:https://my.oschina.net/badboy2/blog/488567

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值