算法拆分
-
遍历树
广度优先遍历(层序遍历) – 辅助栈 Stack// 辅助栈 Stack<TreeNode> stack = new Stack<>(); // 遍历前压栈进根节点 stack.push(root); // 遍历树,广度优先遍历, 每次遍历前 pop 一个元素 while (!stack.isEmpty()) { /* 存在一颗树 1 / \ 2 3 / \ / \ 4 5 6 7 遍历至根节点1,pop(1)。 发现1下有 2, 3 节点 依次 push(2) push(3)。 由于存在压栈的先后顺序, 4 5 6 7 一定需要在 2 3给遍历完后才能遍历 */ TreeNode node = stack.pop(); if (node.left != null) { stack.push(node.left); } if (node.right != null) { stack.push(node.right); } }
-
记录 子节点 -> 根节点 的映射关系 – HashMap
// 存储节点关系 Map<TreeNode, TreeNode> parent = new HashMap<>(); // 在层序遍历的前提下,以 HashMap<child, root> 的形式实现二对一的映射关系。 while (!stack.isEmpty()) { // 栈顶元素 TreeNode node = stack.pop(); if (node.left != null) { parent.put(node.left, node); stack.push(node.left); } if (node.right != null) { parent.put(node.right, node); stack.push(node.right); } }
-
找到两节点的最近交集 - set
// 辅助集合 -- 不需要记录顺序信息 -- 原因 : map + stack 在遍历的时候即可保证顺序性 Set<TreeNode> ancestors = new HashSet<>(); while (p != null) { ancestors.add(p); // 将 p 之前的所有根节点放入集合 p = parent.get(p); } // 遍历 q 之前所有的根节点(从最近至最远), // 若一旦在辅助集合中发现根节点,则一定为最近节点 while (!ancestors.contains(q)) q = parent.get(q); return q;
-
整体 – !parent.containsKey§ || !parent.containsKey(q) 对 !stack.isEmpty() 的一种优化
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { // 辅助栈 Stack<TreeNode> stack = new Stack<>(); // 存储节点关系 Map<TreeNode, TreeNode> parent = new HashMap<>(); // key为节点,value为根 parent.put(root, null); stack.push(root); /* 1 / \ 2 3 / \ / \ 4 5 6 7 */ // 遍历树,广度优先遍历,如果p和q都已遍历,则可以提前结束 -- 【属于一种优化】 while (!parent.containsKey(p) || !parent.containsKey(q)) { // 栈顶元素 TreeNode node = stack.pop(); if (node.left != null) { parent.put(node.left, node); stack.push(node.left); } if (node.right != null) { parent.put(node.right, node); stack.push(node.right); } } // 辅助集合 -- 不需要记录顺序信息 -- 原因 : map + stack 在遍历的时候即可保证顺序性 Set<TreeNode> ancestors = new HashSet<>(); while (p != null) { ancestors.add(p); // 将 p 之前的所有根节点放入集合 p = parent.get(p); } // 遍历 q 之前所有的根节点(从最近至最远), // 若一旦在辅助集合中发现根节点,则一定为最近节点 while (!ancestors.contains(q)) q = parent.get(q); return q; }
-
拓展 – 本题的最优解是递归算法
import com.james.leetcode.TreeNode;
/**
* @Author james
* @Description
* @Date 2019/9/2
*/
public class Solution1 {
private TreeNode ans;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
traverseTree(root, p, q);
return ans;
}
public Solution1() {
this.ans = null;
}
private boolean traverseTree(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return false;
}
// 当前节点为目标节点,染色
int mid = (root == p || root == q) ? 1 : 0;
// 看左节点情况
int left = traverseTree(root.left, p, q) ? 1 : 0;
// 看右节点情况
int right = traverseTree(root.right, p, q) ? 1 : 0;
// 如果左右节点都存在目标节点,则该节点即为最近公共节点
if (left + right + mid >= 2) {
ans = root;
}
return (left + right + mid > 0);
}
public static void main(String[] args) {
TreeNode var_3 = new TreeNode(3);
TreeNode var_5 = new TreeNode(5);
TreeNode var_1 = new TreeNode(1);
TreeNode var_6 = new TreeNode(6);
TreeNode var_2 = new TreeNode(2);
TreeNode var_0 = new TreeNode(0);
TreeNode var_8 = new TreeNode(8);
//
//
TreeNode var_7 = new TreeNode(7);
TreeNode var_4 = new TreeNode(4);
var_3.left = var_5;
var_3.right = var_1;
var_5.left = var_6;
var_5.right = var_2;
var_1.left = var_0;
var_1.right = var_8;
var_6.left = var_7;
var_6.right = var_4;
System.out.println(new Solution1().lowestCommonAncestor(var_3, var_5, var_4).val);
}
}