zoukankan      html  css  js  c++  java
  • 数据结构问题集锦

    作为一个工程党,在各路竞赛大神的面前总会感到自己实力的捉急。大神总是能够根据问题的不同,轻而易举地给出问题的解法,然而我这种渣渣只能用所谓的”直观方法“聊以自慰,说多了都是泪啊。However,正视自己理论方面的不足,迎头赶上还是必要的,毕竟要真正踏入业界,理论知识是不能少的啊。(比如各种语言的Hash Map,它们的核心可都是红黑树啊)

    既然助教要求博文要直观,通俗易懂,那就让我们递归这种方法开始。方法一:递归法

    按照题目的要求,如果某两个节点具有同一个公共祖先的话,那么会存在两种情况:要么其中一个就是公共祖先,而另一个在它的子树里;要么两个节点分别在公共节点的左右子树中。(什么,两个节点在公共节点的同侧子树中?那样的话某侧的直接子节点不就也成公共节点了么?)这样,我们就可以如下设计自己的程序:

    class Solution {
    public:
        TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
            //Tail end of the tree, nothing found
            if (!root)
                return NULL;
            //p or q found, return non-NULL value as signal
            if ((root==p)||(root==q))
                return root;
            
            //Find p or q on left and right branch
            TreeNode* r_left = this->lowestCommonAncestor(root->left, p, q);
            TreeNode* r_right = this->lowestCommonAncestor(root->right, p, q);
    
            //p and q found respectively on two branches, return root as result
            if (r_left && r_right)
                return root;
            //Only one branch contains target node, return non-NULL value as signal
            else if (r_left)
                return r_left;
            else
                return r_right;
        }
    };

    该程序采用递归方式执行。首先,针对传入的节点而言,如果它是空节点,表示已经达到了树的末端,但没有找到p或者q,于是返回null表示没有找到。如果root就是p或者q,则表示我们找到p或q了,返回p或q表示在当前递归路径上找到了p或者q。对于递归过程中间经过的路径而言,如果左右分支都有返回节点,那么根据上面的分析,皆大欢喜,root就是我们要找的结果。如果左右中只有一个分支返回了非null的signal,那么就返回找到的节点,表示我这个分支上还是有找到节点的。程序中当然也隐含了两边分支都没找到节点,同时返回null的情况,这时返回上一层的必然是null(即表示没找到)。

    显然,在最倒霉的情况下,该方法有可能需要访问到所有节点,如果以n代表节点个数的话,最大复杂度可达O(n)。

    方法二:遍历法

    再想想看,遍历整个树也不失为一种不错的做法。在遍历树节点的过程中,我们可以维护一个包含有逐级节点的栈,分别表示从当前节点一直往上到根的路径,在找到p与q时比较两个栈,那么最小公共祖先就很容易找到了。(理论上不需要刻意维护一个栈的,因为函数调用(递归)本身就有调用栈,但是这个无关紧要的问题偷偷懒我想并无大碍吧)

     1 class Solution {
     2 public:
     3     bool Traverse(TreeNode* root, TreeNode* target, vector<TreeNode*>& stack)
     4     {   stack.push_back(root);
     5         
     6         if (root==target)
     7             return true;
     8         else
     9         {   bool result;
    10             
    11             if ((root->left)&&(Traverse(root->left,target,stack)))
    12                 return true;
    13             else if ((root->right)&&(Traverse(root->right,target,stack)))
    14                 return true;
    15             stack.pop_back();
    16             return false;
    17         }
    18     }
    19 
    20     TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
    21     {   vector<TreeNode*> stack_p, stack_q;
    22         unsigned int min_stack_size;
    23         unsigned int i = 0;
    24         TreeNode* result = NULL;
    25         
    26         Traverse(root,p,stack_p);
    27         Traverse(root,q,stack_q);
    28         
    29         min_stack_size = min(stack_p.size(),stack_q.size());
    30         while ((i<min_stack_size)&&(stack_p[i]==stack_q[i]))
    31         {   result = stack_p[i];
    32             i++;
    33         }
    34         
    35         return result;
    36     }
    37 };

    这个方法的复杂度也是O(n),在Leetcode上执行速度貌似和上一个方法差不多。

    方法三:建立反向索引

    现在让我们思考一种情况,如果我们要多次对同一棵树查询最小公共祖先呢?显然在这种情况下,每次调用前两种方法中任一种进行,并不是太经济。这时候我们可以对已有的树进行扩充,为每一个节点建立指向其父节点的反向索引,这样对任两个节点查询最小公共祖先就会变得有效率的多。

    限于Leetcode限定了树的节点的数据结构,并且C++运行时不能够扩充数据类型的成员(所以动态语言大法好),这里就不贴代码了。简要思路就是首先遍历整棵树,除去根节点之外,为其它所有节点建立指向父节点的指针。然后从两个给定节点向上查询,分别构成两个前驱序列(说的很玄其实跟上一问得到两个栈是完全一样的),再找最小公共祖先。

    建立这样一个反向索引的复杂度为O(n),所以对只运行一次的情况这不是经济的做法,然而多次的情况下,该方法的复杂度往往会比前两种低。若令树的层数为m,则每次查询复杂度为O(m),只要树别丧心病狂到长得像链表(换言之,比较”平衡“,m不超过几倍log(n)),方法三的优势还是能够体现的。

    小建(yi)议(yin)

    Coding Jump实在木有存在的必要,为何不用Github Classroom来布置作业呢?什么,你说没有办法自动判作业?Travis CI这种自动构建工具可以办到啊,做个Web Hook,每当有人提交作业就触发Travis CI编译跑样例,然后输出测试结果登分嘛。

  • 相关阅读:
    redis 五种数据结构详解(string,list,set,zset,hash)
    推荐一个同步Mysql数据到Elasticsearch的工具
    一些经验,用来鉴别不太靠谱的公司或工作(面试是双向的,是你最好的了解这个公司的机会)
    OpenSSL 使用 base64 编码/解码(liang19890820)
    Qt之QEvent(所有事件的翻译)
    Go 在 Windows 上用户图形界面 GUI 解决方案 Go-WinGUI 国产(使用cef 内核)
    卷积神经网络CNN
    Event Driven Architecture
    wineshark分析抓取本地回环包
    僵尸进程与孤儿进程
  • 原文地址:https://www.cnblogs.com/lqf-96/p/lowest-common-ancestor-of-a-binary-tree.html
Copyright © 2011-2022 走看看