题目
题目来源:力扣(LeetCode
给定一个二叉树(具有根结点 root), 一个目标结点 target ,和一个整数值 K 。
返回到目标结点 target 距离为 K 的所有结点的值的列表。 答案可以以任何顺序返回。
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], target = 5, K = 2
输出:[7,4,1]
解释:
所求结点为与目标结点(值为 5)距离为 2 的结点,
值分别为 7,4,以及 1
注意,输入的 “root” 和 “target” 实际上是树上的结点。
上面的输入仅仅是对这些对象进行了序列化描述。
思路分析
解法一: 深度优先搜索 + 反向设置深度
- 找距离 target 为 k 的节点,其实就是找 以 target 为根节点,深度为 k 的节点;
- 首先使用深度优先搜索,找到目标节点 target,然后将 target 作为根节点,从 target 出发,使用深度优先搜索去寻找与 target 距离为 k 的所有节点,即深度为 k 的所有节点;
- 通常,node 每向上回溯一层,DFS 的限制深度就会减一,直至根节点,深度变为 0 (树从根结点开始往下数,叶子结点所在的最大层数称为 树的深度);
我们反过来想,将 target 节点的深度置为 k,那么以 target 为根节点的树中,每向下一层,深度 减一,直至深度为 0 的节点,就是我们要找的节点。
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {TreeNode} target
* @param {number} k
* @return {number[]}
*/
var distanceK = function(root, target, k) {
if (!root) return [];
let targetNode = null;
let res = [];
// 在递归过程中,存储已访问过的节点
let paths = [];
// 找到 target 节点,将其存储到 targetNode 中
findTarget(root, target);
// 以target 节点为根节点,向下寻找距离为 k 的节点
getDownDis(targetNode, k);
// 以 target 为根节点,向上寻找距离为 k 的节点
while(targetNode.parent && k > 0) {
targetNode = targetNode.parent;
getDownDis(targetNode, --k);
}
function findTarget(root, target) {
if (!root || targetNode) return;
// 找到 target 节点,存储到 targetNode 中
if (root.val === target.val) {
targetNode = root;
}
// 从左子树中找 target 节点
if (root.left) {
// 记录左孩子的父节点
root.left.parent = root;
findTarget(root.left, target);
}
// 从右子树中找 target 节点
if (root.right) {
// 记录右孩子的父节点
root.right.parent = root;
findTarget(root.right, target);
}
}
// node每向上回溯一层,DFS的限制深度就会减1,直到根节点root。若target在树中的深度为h, 且位于左子树中, 那么最后一次DFS就是在root与其右子树部分找出深度为k-h的所有节点
// 寻找离目标节点 target 的距离为 k 的节点:那么以 target 为根的树中,深度为 k 的所有节点显然符合要求
function getDownDis(node, k) {
// 已访问过的节点,不再访问
if (node === null || paths.indexOf(node) !== -1) return;
// 将未访问过的节点存储到 paths 中
paths.push(node);
if (k > 0) {
// 通常,node 每向上回溯一层,DFS 的限制深度就会减一,直至根节点,深度变为 0 (树从根结点开始往下数,叶子结点所在的最大层数称为 树的深度)
// 我们反过来想,将 target 节点的深度置为 k,那么以 target 为根节点的树中,每向下一层,深度 减一,直至深度为 0 的节点,就是我们要找的节点
getDownDis(node.left, k - 1);
getDownDis(node.right, k - 1);
} else if (k === 0) {
res.push(node.val);
}
}
return res;
};
解法二:深度优先搜索 + 哈希表
- 我们将 target 作为根节点,从 target 出发,使用深度优先搜索寻找与 target 距离为 k 的所有节点,即深度为 k 的所有节点
- 由于输入的二叉树没有记录父节点,因此,我们从根节点 root 出发,使用深度优先搜索遍历整棵树,同时用一个哈希表记录每个节点的父节点
- 然后从 target 出发,使用深度优先搜索遍历整棵树,除了搜索左右子树外,还可以顺着父节点向上搜索
- 由于每个节点的值都是唯一的,因此哈希表的键可以用节点值代替。此外,为了避免在深度优先搜索时重复访问节点,递归是额外传入来源节点 from,在递归前比较目标节点是否与来源节点相同,不同的情况下才进行递归
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {TreeNode} target
* @param {number} k
* @return {number[]}
*/
var distanceK = function(root, target, k) {
const parents = new Map();
const ans = [];
const findParents = (node) => {
// DFS 左子树,记录每个节点的父节点
if (node.left != null) {
parents.set(node.left.val, node);
findParents(node.left);
}
// DFS 右子树,记录每个节点的父节点
if (node.right != null) {
parents.set(node.right.val, node);
findParents(node.right);
}
}
// 从 root 出发,深度优先搜索,记录每个节点的父节点
findParents(root);
const findAns = (node, from, depth, k) => {
if (node == null) return;
// 找到距离为 k 的节点
if (depth === k) {
ans.push(node.val);
return;
}
// 在左子树中寻找距离为k (即深度为k) 的节点
if (node.left !== from) {
findAns(node.left, node, depth + 1, k);
}
// 在右子树中寻找距离为k (即深度为k) 的节点
if (node.right !== from) {
findAns(node.right, node, depth + 1, k);
}
// 顺着父节点向上寻找距离为k (即深度为k) 的节点
if (parents.get(node.val) !== from) {
findAns(parents.get(node.val), node, depth + 1, k);
}
}
// 以 target 为根节点,从 target 出发,寻找所有深度为 k 的节点,根节点 target 的深度为 0
findAns(target, null, 0, k);
return ans;
};