`
frank-liu
  • 浏览: 1682135 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

leetcode: Lowest Common Ancestor of a Binary Tree

 
阅读更多

问题描述

Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.

According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w as descendants (where we allow a node to be a descendant of itself).”

        _______3______
       /              \
    ___5__          ___1__
   /      \        /      \
   6      _2       0       8
         /  \
         7   4

For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3. Another example is LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.

原问题链接:https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/

 

问题分析

    这个问题和前面类似的求二叉搜索树不一样,它相对要难一些。因为在前面基于二叉搜索树查找最低父节点的时候可以根据当前节点的值和两个目标节点做比较,然后根据它们值的大小作下一步的选择。而这里的数据结构不在是二叉搜索树,没法根据每个节点的值来作为判断的依据了。那么该怎么来处理这个问题呢?

 

方法一:

    这种办法是基于这么一个设想。对于一棵二叉树来说,如果需要找两个节点的最低公共父节点,假设它们有指向父节点的引用,那么可以先从一个节点开始,不断向上遍历一直到根节点,并将这些遍历过的节点通过某个数据结构保存起来。然后从另外一个节点也这样开始向上遍历,每次遍历的节点和前面那个节点遍历的所有节点比较,如果有碰到第一个相同的则表示这个节点就是我们要找到目标节点。比如说我们有如下的树:

 

     如果按照上述的思路,我们的节点8往上到根节点遍历的节点是{8, 4, 2, 1},而节点6往上遍历到根节点的所有节点是{6, 3, 1}。所以它们的最低公共父节点是1,也就是根节点。

    现在,假设我们已经有了一个节点到根节点的遍历路径,我们可以用一个集合(Set)来保存它们,对于另外一个节点,只要它往上遍历的时候碰到的第一个在前面集合中存在的元素,则找到了目标节点。

    上面这部分能够得以进行是基于一个假设的前提,就是可以通过某种途径来得到每个节点的父节点信息。在现在的二叉树结构里并没有直接提供这个引用。那么该怎么获得这个信息呢?另外,获得了这个信息之后,又该怎么来保存它和它的父节点信息以方便从该节点往上遍历呢?这个时候就需要借用一下一些二叉树遍历的方法了。 

    在所有二叉树遍历的算法过程中,如果用非递归的方式来遍历的话,最简单的大概就算是层次化的遍历树了。关于二叉树的层次化遍历在我之前的文章中也有讨论过。为什么要选择这种层次化遍历的方式呢?因为一个就是如果递归的去遍历一些树结构在树的层次比较深的时候容易导致递归的堆栈溢出,而采用层次化遍历不会有这个问题。另外一个,我们在遍历每个节点的时候需要将该节点和它的子节点关系建立起来并保存到一个结构中,采用这种方式比较直观。每次遍历一层的时候就将该层的节点和它们字节点映射起来。

    在怎么去遍历树的的方式定下来之后,现在需要定下来的就是怎么保存每个节点和它们父节点的关系了。一种比较有效的保存办法就是利用一个map,里面的key和value都是TreeNode。为什么选择用map呢?因为对于树里面的每个节点,它都是唯一的,如果以它们作为key是不会存在有冲突的。另外,它们每个节点都有父节点,对于根节点这个特殊情况,我们可以定义它的父节点为null。如果我们要从某个往上遍历的时候只需要node = map.get(node)就可以得到了。通过这样的不断循环来向上走。

     通过这一通讨论,我们有了一个基本的思路。概括起来如下,首先通过层次遍历将每个节点和它的子节点之间的关系映射到一个map里。然后通过一个节点利用map的映射关系向上遍历一直到根节点。将它遍历过的所有节点保存在一个set里。然后另外一个也利用map的映射关系向上遍历,但是遍历的时候判断自己遍历的元素是否在前面的set中,如果找到了,则该节点就是最低公共父节点。按照这个思路实现的代码如下:

 

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null || p == null || q == null) return null;
        Map<TreeNode, TreeNode> map = buildPath(root);
        Set<TreeNode> set = new HashSet<>();
        TreeNode node = p;
        while(node != null) {
            set.add(node);
            node = map.get(node);
        }
        node = q;
        while(node != null) {
            if(set.contains(node)) return node;
            node = map.get(node);
        }
        return null;
    }
    
    Map<TreeNode, TreeNode> buildPath(TreeNode node) {
        Map<TreeNode, TreeNode> map = new HashMap<>();
        map.put(node, null);
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(node);
        while(!queue.isEmpty()) {
            TreeNode tNode = queue.remove();
            if(tNode.left != null) {
                map.put(tNode.left, tNode);
                queue.add(tNode.left);
            }
            if(tNode.right != null) {
                map.put(tNode.right, tNode);
                queue.add(tNode.right);
            }
        }
        return map;
    }
}

    上述代码的时间复杂度也比较好推导,首先一个层次遍历,它的时间复杂度为O(N),然后一个节点向上遍历,时间复杂度也为O(N),另外一个节点的过程也类似。所以总体时间复杂度为O(N)。而空间复杂度主要是包含几个部分,首先是层次化遍历的时候要用到一个queue,空间为O(N),保存映射关系的时候用到一个map,空间为O(N),保存遍历路径节点的set,空间为O(N),总体也是在O(N)的范围。所以总体来说这种方法的时间和空间复杂度还是在一个比较合理的范围。

 

方法二:

    上述的第一种方法总的来说还是步骤有点复杂,因为需要去遍历树来建立映射关系再获取从子节点到父节点的路径。有些地方如果没有想到的话还是比较难解决的。那么还有没有其他的思路呢?实际上还有一种比较简单直观的思路。就是递归的方式。我们可以这样来看,对于任意的一个节点来说,如果以它为根节点去找两个节点的最低公共父节点,首先需要的是找到它的左右子树中是否存在有要查找的两个节点中的一个。如果找到任意一个则返回,否则返回null。而在回溯的时候,对于这个最低公共子节点有一个特性,它必然左右子节点返回的值都不为空的,而其他节点则总会有一个为空。所以在回溯的时候,如果它们中间有一个非空则返回不空的那个,如果左右子树的节点都不空则返回当前节点。这样可以得到如下的代码:

 

public class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) { return root; }
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if (left!=null && right!=null) { return root; }
        return left!=null ? left : right;
    }
}

    这种方式实现的效率相对来说更高。因为它总的来说只需要遍历树一遍,然后在回溯的时候将结果过滤出来。它的时间复杂度也是O(N)。从使用的空间来说,因为要用到堆栈来递归的调用,在树的层次比较深的时候容易造成栈溢出。当然,这种实现的方式是最简洁的。

 

总结

     求二叉树的最低公共父节点算是一个讨论过很多的问题了。对于它的解法有很多。对于第一种遍历树记录节点父子关系,然后再通过节点遍历来查找的方式比较容易想到一点。只是用到各种数据结构会比较繁琐。而第二种通过递归回溯求解的时候需要定义清楚它们的递归关系并且回溯的时候对结果进行过滤。这种方式比较巧妙,值得反复推敲。

 

参考材料

http://www.geeksforgeeks.org/lowest-common-ancestor-binary-tree-set-1/

http://articles.leetcode.com/lowest-common-ancestor-of-a-binary-tree-part-i

http://articles.leetcode.com/lowest-common-ancestor-of-a-binary-tree-part-ii

 

  • 大小: 13.2 KB
分享到:
评论

相关推荐

    java-leetcode题解之Lowest Common Ancestor of a Binary Tree.java

    java java_leetcode题解之Lowest Common Ancestor of a Binary Tree.java

    c语言-leetcode题解之0236-lowest-common-ancestor-of-a-binary-tree

    c c语言_leetcode题解之0236_lowest_common_ancestor_of_a_binary_tree

    c语言-leetcode题解之0235-lowest-common-ancestor-of-a-binary-search

    c c语言_leetcode题解之0235_lowest_common_ancestor_of_a_binary_search

    leetcode:LeetCode练习

    4. **树结构**:如“二叉树的最近公共祖先”(Lowest Common Ancestor of a Binary Tree),需要找到给定节点的最近公共祖先。 5. **动态规划**:如“最长连续序列”(Longest Consecutive Sequence),要求找到一个...

    LeetCode:AC源代码

    "二叉树的最近公共祖先"(Lowest Common Ancestor of a Binary Tree)可以用递归或迭代的方法解决。 5. **图**:图问题可能涉及到深度优先搜索(DFS)、广度优先搜索(BFS)等。例如,"最短路径"(Shortest Path in...

    leetcode分类-leetcode:leetcodeJavaScript题解

    例如,"二叉树的最近公共祖先"(Lowest Common Ancestor of a Binary Tree)题目,需要理解深度优先搜索或层次遍历的方法。 高级算法: 高级算法通常涉及到更深层次的思考和优化,如位运算、贪心策略、分治法等。...

    Daily_Leetcode:每天一个Leetcode问题

    问题235:“最近的公共祖先(Lowest Common Ancestor of a Binary Search Tree)” 这是一个关于二叉搜索树的问题。在二叉搜索树中,每个节点的值都大于其左子树中的所有节点值,小于其右子树中的所有节点值。这个问题...

    Leetcode综合题解md版.zip

    比如"二叉树的最近公共祖先"(Lowest Common Ancestor of a Binary Tree)需要掌握树的遍历策略,而"有效的井字游戏"(Valid Tic-Tac-Toe)则需要理解博弈论的Nim游戏规则。 每道题的Markdown文件通常包含以下几个...

    leetcode常见的100热点算法题

    5. **二叉树与图**:二叉树题目如"Binary Tree Inorder Traversal"(二叉树的中序遍历)和"Lowest Common Ancestor of a Binary Tree"(二叉树的最近公共祖先),图题目如"Shortest Path in Bidirectional Graph"...

    Leetcode部分试题解析

    12. **Lowest Common Ancestor of a Binary Search Tree**:二叉搜索树的最近公共祖先。利用二叉搜索树的性质,可以有效地向上遍历找到最近公共祖先。 13. **Product of Array Except Self**:不包含自身的数组乘积...

    [数据结构与算法]JAVA二叉树题目总结(包含常见题目部分LeetCode题解)

    Lowest Common Ancestor of a Binary Search Tree等。通过解决这些题目,你可以加深对二叉树的理解,并提升解决问题的能力。 总结,Java中的二叉树题目不仅要求对数据结构有深入理解,还需要灵活运用算法。不断...

    leetcode答案-CodeBase:基本算法问题

    2. 树形结构:树相关的题目如“Binary Tree Inorder Traversal”(二叉树的中序遍历)和“Lowest Common Ancestor of a Binary Tree”(二叉树的最近公共祖先)等,要求对二叉树的遍历和搜索有深入理解。 3. 排序与...

    lrucacheleetcode-LeetCode_Java:力扣_Java

    lru缓存leetcode 力码 如果你在这里找不到答案,你可以移到 1. BFS/拓扑/图 2.联合查找 3. 二分查找 4. 动态规划 Double Sequence Problem (双序列) Knapsack Problem (背包问题) --- Binary Lifting for LCA...

    leetcode卡-leet-code-may-challenge:包含对MayChallenge(LeetCode)上发布的问题的解决方案。

    2. 二叉树:例如“Lowest Common Ancestor of a Binary Tree”涉及到二叉树的遍历和节点查找。 3. 图论:如“Course Schedule II”可能涉及拓扑排序和深度优先搜索。 4. 字符串处理:如“Valid Palindrome II”要求...

    Algorithms-Leetcode-Javascript:Javascript中的算法解析。 Leetcode-Geeksforgeeks-职业生涯

    要在控制台中运行特定问题,请转至文件test,将调用添加到测试函数( test() ),然后在控制台node &lt;problem&gt; (例如, node LeetcodeProblems/Lowest_Common_Ancestor_of_a_Binary_Tree.js )。 Leetcode问题 名称...

    leetcode和oj-leetcode-java:力扣在线裁判解决方案

    leetcode 和 oj LeetCode 解决方案 Java 版 Leetcode 问题分类 细绳 8 字符串到整数 (atoi) 14 ...oj_102_binary_tree_level_order ...oj_236_lowest_common_ancestor_of_bt / notes.md SRC /溶液/ oj

    Leetcode-best-DSA-问题:必须执行这些leetcode编码问题以提高您的问题解决能力

    如“二叉树的最近公共祖先”(Lowest Common Ancestor of a Binary Tree)和“拓扑排序”(Topological Sort)。 8. **动态规划**:动态规划用于解决具有重叠子问题和最优子结构的问题,如“背包问题”(Knapsack ...

    LeetCode

    例如,“二叉树的最近公共祖先”(Lowest Common Ancestor of a Binary Tree)问题,要求找到二叉树中两个节点的最近公共祖先。 8. **动态规划**:这是一种优化问题解决的策略,常用于解决背包问题、最长公共子序列...

    手稿_V1.093

    在给定的文件中,我们讨论的是一个与计算机科学相关的编程问题,具体是关于二叉搜索树(Binary Search Tree, BST)的最近公共祖先(Lowest Common Ancestor, LCA)问题。这个问题来源于LeetCode的一个挑战,其目标是...

Global site tag (gtag.js) - Google Analytics