LeetCode 236:二叉树的最近公共祖先

 

 

特别说明:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。

 

解题思路:

  一般二叉树相关的算法题,都可以使用递归这个编程技巧来解题,本题也不例外。

 

分析: 2个节点存在的位置,不外乎以下4种情况:

  • 要么都在左子树上
  • 要么都在右子树上
  • 要么一个在左,一个在右
  • 至少有一个不在这颗二叉树中【就是不在这个二叉树上的情况】

我们先看代码,然后来讲解解法,代码如下:

 
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     public var val: Int
 *     public var left: TreeNode?
 *     public var right: TreeNode?
 *     public init(_ val: Int) {
 *         self.val = val
 *         self.left = nil
 *         self.right = nil
 *     }
 * }
 */
 
class Solution {
    func lowestCommonAncestor(_ root: TreeNode?, _ p: TreeNode?, _ q: TreeNode?) -> TreeNode? {
        if root == nil || root?.val == p?.val || root?.val == q?.val { return root }
        let left = lowestCommonAncestor(root?.left, p, q)
        let right = lowestCommonAncestor(root?.right, p, q)
        if left != nil && right != nil { return root }
        return left == nil ? right : left
    }
}

 

代码解析:
先是边界条件的判断
   1. if root为nil,直接 return
   2. if root.val == q.val 或者 root.val == p.val,直接 return

下面是递归逻辑:
   1. 先从左子树上找共同的祖先,记为left
   2. 再从右子树上找共同的祖先,记为right

   如果left和right都不为nil,说明满足上述的条件3【一个在左,一个在右】,所以很显然,祖先节点就是根节点root;

   如果left不为nil,则返回left,否则返回right。这里面就包含了剩下3种条件,left不为nil,说明2个节点都在左子树上。如果left为nil,则说明要么2个节点都在右子树上,或者至少有一个不在这个二叉树上。

 

关于递归:
   递归这种编程技巧是非常有用的,掌握它,可以为我们解决很多思考起来很麻烦的题目。为了更好的理解递归,大家可以看我之前写的文章:leetcode 递归编程技巧-链表算法题。为了让大家体会到递归的强大,我们再看1道题: LeetCode 144. 二叉树的前序遍历
如下图:一颗二叉数

 

 

前序遍历就是:先访问根节点、前序遍历左子树、前序遍历右子树。


过程如下:

先访问5,然后使用中序遍历去访问 5.left,再然后使用前序遍历去访问 5.right。
1.1. 中序遍历去访问5.left
先访问3,然后使用中序遍历去访问 3.left,再然后使用前序遍历去访问 3.right。
...
...
1.2. 中序遍历去访问5.right
先访问7,然后使用中序遍历去访问 7.left,再然后使用前序遍历去访问 7.right。
...
...

打印结果就是:5-3-1-0-2-4-7-6-8-9

代码:

 
class Solution {
    var nums = [Int]()
    func inorderTraversal(_ root: TreeNode?) -> [Int] {
    guard let root = root else { return [] }
            nums.append(root.val)
            inorderTraversal(root.left)
            inorderTraversal(root.right)
    return nums
        }
}

 

代码解析:
先是边界条件的判断
   1. 先访问根节点
   2. 然后前序遍历左子树
   2. 然后前序遍历右子树

如果问你中序遍历【左根右】,你会写吗?

class Solution {
    var nums = [Int]()
    func inorderTraversal(_ root: TreeNode?) -> [Int] {
    guard let root = root else { return [] }
            inorderTraversal(root.left)
            nums.append(root.val)
            inorderTraversal(root.right)
    return nums
        }
}

对,你没看错。就是这么简单,把它的访问放在中间就是【以后写一些算法的时候,可以把逻辑写在中间】。那么后序遍历了?一样,把访问 nums.append(root.val) 代码放在最后一行就是了。这就是递归的魅力。

 

总结:
  今天主要讲了一道leetcode上面的第236道算法题,其核心思想就是使用递归来实现的。后续为了让大家体会到递归编程技巧的强大,我又借二叉树的遍历例子用递归来实现为大家展示递归的强大。大家如果想更好的理解递归的调用栈的话,可以看我自己写的文章:leetcode 递归编程技巧-链表算法题。希望能帮助到大家。

 

欢迎关注【无量测试之道】公众号,回复【领取资源】
Python编程学习资源干货、
Python+Appium框架APP的UI自动化、
Python+Selenium框架Web的UI自动化、
Python+Unittest框架API自动化、
资源和代码 免费送啦~
文章下方有公众号二维码,可直接微信扫一扫关注即可。

备注:我的个人公众号已正式开通,致力于测试技术的分享,包含:大数据测试、功能测试,测试开发,API接口自动化、测试运维、UI自动化测试等,微信搜索公众号:“无量测试之道”,或扫描下方二维码:


添加关注,让我们一起共同成长!

posted on 2021-08-18 15:19  Wu_Candy  阅读(62)  评论(0编辑  收藏  举报