【算法】求二叉树两节点的最近公共祖先 迭代解法 Stack + Map + Set

25 篇文章 1 订阅
14 篇文章 0 订阅

算法拆分

  • 遍历树
    广度优先遍历(层序遍历) – 辅助栈 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);

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值