二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉树中。
题目来源:https://leetcode-cn.com/explore/learn/card/data-structure-binary-tree/4/conclusion/19/
使用递归
1)递归(recursion)其实是一种深度优先搜索策略(dfs);这里使用“自底向上”的方案,因为从叶子节点向上搜索得到的第一个公共祖先是深度最大的。
2)首先递归到最底层便可从叶子节点开始搜索回调。
3)若某个节点即为两个目标节点的最近公共祖先,应满足的条件为:该节点的左右子树中分别存在一个目标节点;或者该节点即为目标节点,并另外一个目标节点存在其左或右子树当中。
代码如下:
1)创建节点
// 节点构造
public class TreeNode {
private int data;
private TreeNode leftChild; // 左孩子指针
private TreeNode rightChild; // 右孩子指针
// 构造方法
public TreeNode(){}
public TreeNode(int data) {
this.data = data;
}
// setter and getter方法
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public TreeNode getLeftChild() {
return leftChild;
}
public void setLeftChild(TreeNode leftChild) {
this.leftChild = leftChild;
}
public TreeNode getRightChild() {
return rightChild;
}
public void setRightChild(TreeNode rightChild) {
this.rightChild = rightChild;
}
}
2)根据数组得到二叉树,并返回根节点。
import java.util.LinkedList;
import java.util.List;
public class BinaryTree {
private TreeNode root;
public BinaryTree(){}
public BinaryTree(TreeNode root){
this.root = root;
}
// 传入一个数组,构造二叉树方法,返回根节点
public TreeNode createTree(int[] array){
if(array == null){
return null;
}
// 将数组元素转换为树节点值
List<TreeNode> nodeList = new LinkedList<>();
for (int i =0; i<array.length; i++){
nodeList.add(new TreeNode(array[i]));
}
root = nodeList.get(0); // 跟节点
for (int i = 0; i<array.length/2-1; i++){
// 节点的左孩子
nodeList.get(i).setLeftChild(nodeList.get(2*i+1));
// 节点的右孩子
nodeList.get(i).setRightChild(nodeList.get(2*i+2));
}
// 最后一个有孩子的父节点存在两种情况
if (array.length != 1){
int index = array.length/2-1;
nodeList.get(index).setLeftChild(nodeList.get(2*index+1));
// 有右孩子的条件
if (array.length%2 == 1){
nodeList.get(index).setRightChild(nodeList.get(2*index+2));
}
}
return this.root;
}
}
3)获得二叉树的最近祖先
public class Solution {
private TreeNode ans;
// 寻找二叉树最先公共祖先方法,使用递归,自低向上方案
public boolean recursion(TreeNode root, TreeNode p, TreeNode q){
if (root == null){
return false;
}
// 递归直到最底层,从最底层开始回调寻找
// 遍历左子树
boolean leftChild = recursion(root.getLeftChild(), p, q);
// 遍历右子树
boolean rightChild = recursion(root.getRightChild(), p, q);
// 该节点为最近祖先,赋给ans保存,该程序只进入一次,得到最近祖先的时候。
if (leftChild && rightChild || (root.getData() == p.getData() || root.getData() == q.getData()) && (leftChild || rightChild)){
ans = root;
}
// 该节点与其子树存在一个以上的目标节点,返回true,否则返回false。
return (leftChild || rightChild || root.getData() == p.getData() || root.getData() == q.getData());
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
this.recursion(root, p, q);
return this.ans;
}
// 测试
public static void main(String[] args) {
BinaryTree bt = new BinaryTree();
int[] array = {1,2,3,4,5,6,7,8,9,10,11,12};
TreeNode root = bt.createTree(array);
Solution solution = new Solution();
TreeNode ans = solution.lowestCommonAncestor(root, root, root.getRightChild());
System.out.println(ans.getData());
}
}
复杂度分析
时间复杂度:O(N)。二叉树有N个节点,并且每个节点会被访问一次,因此时间复杂度为 O(N)。
空间复杂度:O(N) 。一次递归调用所需的最大深度是树的深度,在最坏情况下,二叉树只有一条链,此时高度为就为节点数N,因此空间复杂度为 O(N)。