树的最近公共祖先

文章介绍了如何在二叉树中寻找两个节点的最近公共祖先,提供了两种方法:递归分治和存储父节点的层级遍历。对于二叉搜索树,利用其特性可优化搜索策略。递归方法通过判断当前节点的左右子树来确定公共祖先,而层级遍历则构建节点与父节点的映射,从目标节点向上回溯找到公共祖先。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
剑指 Offer 68 - II. 二叉树的最近公共祖先

二叉树的最近公共祖先

方法一:递归

  • 对于节点node1、node2,其最近公共祖先节点parentNode的特点是以下之一:
    • node1存在与parentNode左子树中,node2存在于parentNode的右子树中
    • node1、node2均存在于parentNode的同一侧子树中,且node1或node2就是parentNode
      在这里插入图片描述
  • 所以,如果当前节点的左子树包含node1且右子树中包含node2,或者只有一侧子树中有node1且当前节点等于另一个node2,就找到了最近公共祖先节点。
  • 所以,递归【判断当前节点的左、右子树中是否含有指定节点】
const dfs (node, p, q) {
	if (!node) {
		return false
	}
	// 判断左子树中是否含有p或q
	const lHas = dfs(node.left, p, q)
	// 判断右子树中是否含有p或q
	const rHas = dfs(node.right, p, q)
	
	return lHas || rHas || node.val === p.val || node.val === q.val
}
// 祖先节点判断
if ((lHas && rHas) || 
	(lHas || rHas) && (node.val === q.val || node.val === p.val)) {
	parentNode = node
}
var lowestCommonAncestor = function(root, p, q) {
    let parentNode = null
    const dfs = (node, p, q) => {
        if (!node) {
            return null
        }

        const lTree = dfs(node.left, p, q)
        const rTree = dfs(node.right, p, q)

        if ((lTree && rTree) || ((node.val === p.val || node.val === q.val) && (lTree || rTree)) ) {
            parentNode = node
        }

        return lTree || rTree || node.val === p.val || node.val === q.val
    }
    dfs(root, p, q)
    return parentNode
};

分治

  • 这里其实用到了分治的思想,对于一棵树操作起来也许不是很容易,把它拆成了操作其左右子树。判断以节点A为根的树是否满足条件,可以先看节点A的左右子树是否满足条件,再操作自身,最后总结左子树、右子树、自己的结果。

剑指 Offer 55 - I. 二叉树的深度
剑指 Offer 55 - II. 平衡二叉树

  • 这两个题中,求树的深度,依次向下拆解为求左右子树的深度,再向上综合结果。
    • 输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
    height(root) {
    	if (!root) { return 0 }
    	// 左右子树最深的作为子树高度
    	// 当前高度 = 子树高度 + 1(自己)
    	return Math.max(height(root.left), height(root.right)) + 1
    }
    

方法二:存储父节点

  • 用Map存储所有节点的父节点。然后从p开始向根节点访问,最后从q开始向上访问,遇到已访问过的节点,说明遇到了最近的公共祖先。
    • 要存储每一个节点的父节点,需要通过层级遍历实现。
    var levelTraversal = (root, p, q) => {
    	// 层级遍历中的队列
        const queue = [root]
        // 保存父子映射
        const relationMap = new Map()
        relationMap.set(root, {
            parent: null,
            visited: false
        })
        let findAll = 0
    	
        while(queue.length) {
            const node = queue.shift()
            if (node.val === p.val || node.val === q.val) {
                findAll++
            }
    		// 简化,不用遍历所有节点;找到p, q就返回
            if (findAll === 2) {
                return relationMap
            }
            if (node.left) {
                relationMap.set(node.left, {
                    parent: node,
                    visited: false
                })
                 queue.push(node.left)
            }
            if (node.right) {
                relationMap.set(node.right, {
                    parent: node,
                    visited: false
                })
                queue.push(node.right)
            }
        }
    }
    
    • 从p,q开始向上访问
    var lowestCommonAncestor = function(root, p, q) {
        if (!root) { return null }
        const relationMap = levelTraversal(root, p, q)
        let pNode = relationMap.get(p)
        let qNode = relationMap.get(q)
    
        if (pNode && qNode) {
            while (pNode && !pNode.visited) {
                pNode.visited = true
                pNode = relationMap.get(pNode.parent)
            }
            while (qNode && !qNode.visited) {
                q = qNode.parent
                qNode = relationMap.get(qNode.parent)
            }
        }
    
        return q
    
    };
    

层级遍历

  • 在寻找路径时,一种方法是使用这种记录节点和其父节点的映射,这种映射通常需要借助层级遍历。

剑指 Offer 34. 二叉树中和为某一值的路径

二叉搜索树的最近公共祖先

  • 二叉搜索树是二叉树的特殊一种,所以以上两种方法也可以适用。
  • 但是,由于二叉树的特点,即左子树都比根节点小,右子树都比根节点大,所以可以在递归上有所变化。
  • 对于root、p、q的值,只有三种情况:
    • p和q都小于root:p和q的最近公共祖先一定在root的左子树中
    • p和q都大于root:p和q的最近公共祖先一定在root的右子树中
    • 其他,即root在p、q之间:说明在root处分岔了,那root就是p和q的最近公共子祖先
      在这里插入图片描述
var lowestCommonAncestor = function(root, p, q) {
    return dfs(root, p, q)
};

var dfs = (node, p, q) => {
    if (!node) { return null }

    if (node.val > p.val && node.val > q.val) {
        return dfs(node.left, p, q)
    }
    if (node.val < p.val && node.val < q.val) {
        return dfs(node.right, p, q)
    }
    return node
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值