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自动化测试等,微信搜索公众号:“无量测试之道”,或扫描下方二维码:
添加关注,让我们一起共同成长!