力扣每日一题-0216-1719. 重构一棵树的方案数

139 阅读4分钟

「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战」。

给你一个数组 pairs ,其中 pairs[i] = [xi, yi] ,并且满足:

  • pairs 中没有重复元素
  • xi < yi

令 ways 为满足下面条件的有根树的方案数:

  • 树所包含的所有节点值都在 pairs 中。
  • 一个数对 [xi, yi] 出现在 pairs 中 当且仅当 xi 是 yi 的祖先或者 yi 是 xi 的祖先。
  • **注意:**构造出来的树不一定是二叉树。

两棵树被视为不同的方案当存在至少一个节点在两棵树中有不同的父节点。

请你返回:

  • 如果 ways == 0 ,返回 0 。
  • 如果 ways == 1 ,返回 1 。
  • 如果 ways > 1 ,返回 2 。

一棵 有根树 指的是只有一个根节点的树,所有边都是从根往外的方向。

我们称从根到一个节点路径上的任意一个节点(除去节点本身)都是该节点的 祖先 。根节点没有祖先。

示例 1:

image.png

输入:pairs = [[1,2],[2,3]]
输出:1
解释:如上图所示,有且只有一个符合规定的有根树。

示例 2:

image.png

输入:pairs = [[1,2],[2,3],[1,3]]
输出:2
解释:有多个符合规定的有根树,其中三个如上图所示。

示例 3:

输入:pairs = [[1,2],[2,3],[2,4],[1,5]]
输出:0
解释:没有符合规定的有根树。

题目给定的数对 pairs[i]=[xi,yi]pairs[i]=[xi,yi],且满足 xixiyiyi· 的祖先或者 yiyixixi 的祖先;树中所包含的所有节点值都在 pairspairs 中,即 pairspairs 包含树中所有可能构成祖先的数对。

设树中节点数目为 n,pairspairs 中包含节点 x 的数对的数目为 degree[x]degree[x],节点 x 的祖先和后代的节点集合为 adj[x]adj[x]

下面来研究 degreedegree 的性质。

  • 根节点为树中其余所有节点的祖先,根节点与其余所有节点都能构成数对。设根节点为 rootroot,由于 pairspairs 包含树中所有可能构成祖先的数对,因此 degree[root]=n1degree[root]=n−1。如下图所示,根节点 111 为其余节点的祖先,蓝色节点组成了 adj[1]adj[1]

image.png

  • 对于 pairspairs 中的数对 [xi,yi][xi,yi],如果 xixiyiyi 的祖先,则一定满足 degree[xi]degree[yi]degree[xi]≥degree[yi]。如果节点 yjyj 为节点 yiyi 的后代节点,则节点 yjyj 一定同时也是节点 xixi 的后代节点;如果节点 yjyj 为节点 yiyi 的祖先节点,则节点 yjyj 要么是节点 xixi 的祖先节点,要么是节点 xixi 的后代节点,所以一定满足 degree[xi]degree[yi]degree[xi]≥degree[yi]。此外,如果 xixiyiyi 的祖先,则一定满足 adj[yi]adj[xi]adj[yi]∈adj[xi]。如下图所示,含有节点 2 的数对数目一定大于含有节点 3 的数对数目。

image.png

  • 对于 pairspairs 中的数对 [xi,yi][xi,yi],如果 xixiyiyi 的祖先,且满足 degree[xi]=degree[yi]degree[xi]=degree[yi]adj[xi]=adj[yi]adj[xi]=adj[yi],则 xixiyiyi 途径的所有节点均只有一个孩子节点。此时 xixiyiyi 之间的节点包含的数对关系是一样的, xixiyiyi 之间的节点是可以进行互相交换而不影响树的结构,则此时构成树的方案数一定不是唯一的。如下图所示,节点 6,7,9 满足上述要求:

image.png

对于 pairspairs 中的数对 [xi,yi][xi,yi]

  • degree[xi]>degree[yi]degree[xi]>degree[yi],则 xixiyiyi的祖先节点;
  • degree[xi]<degree[yi]degree[xi]<degree[yi],则 yiyixixi 的祖先节点;
  • degree[xi]=degree[yi]degree[xi]=degree[yi],则可能存在多种构造方法

通过以上分析结论,我们可以尝试进行重新建树,并检查建成的树是否合法。

  • 首先我们需要找到根节点 rootroot,通过上述结论,我们找到满足 degree[root]=n1degree[root]=n−1 的节点,如果不存在根节点,则认为其不能构成合法的树,返回 0。
  • 我们需要利用上述的结论检测是构建的树是否合法,遍历每个节点 nodeinodei,找到 nodeinodei 的祖先 parentiparenti ,检测集合 adj[nodei]adj[nodei] 是否为 adj[parenti]adj[parenti] 的子集。可以利用 degree[nodei]degree[parenti]degree[nodei]≤degree[parenti] 找到所有属于 nodeinodei 的祖先节点,然后依次检测是否满足 adj[nodei]adj[parenti]adj[nodei]∈adj[parenti],如果不满足要求,则认为构建的树为非法,返回 0。
  • 实际检测过程中不必检测节点 nodeinodei 的所有祖先节点,只需要检测节点 nodeinodei 的父节点是否满足子集包含的要求即可。根据上述推论找到节点 x 满足 degree[x]degree[x] 最小且 degree[nodei]degree[parenti]degree[nodei]≤degree[parenti],则此时找到的节点为节点 nodeinodei 的父亲节点,此时只需检测父亲节点是否满足上述要求即可。
  • nodeinodei 的父节点为 parentparent,若满足 degree[nodei]=degree[parent]degree[nodei]=degree[parent] 则树的构造方式可以有多个,返回 2。
var checkWays = function(pairs) {
    const adj = new Map();
    for (const p of pairs) {
        if (!adj.has(p[0])) {
            adj.set(p[0], new Set());
        }
        if (!adj.has(p[1])) {
            adj.set(p[1], new Set());
        }
        adj.get(p[0]).add(p[1]);
        adj.get(p[1]).add(p[0]);
    }
    /* 检测是否存在根节点*/
    let root = -1;
    const entries = new Set();
    for (const entry of adj.entries()) {
        entries.add(entry);
    }
    for (const [node, neg] of entries) {
        if (neg.size === adj.size - 1) {
            root = node;
        }
    }
    if (root === -1) {
        return 0;
    }
    let res = 1;
    for (const [node, neg] of entries) {
        /* 如果当前节点为根节点 */
        if (root === node) {
            continue;
        }
        const currDegree = neg.size;
        let parentNode = -1;
        let parentDegree = Number.MAX_SAFE_INTEGER;
        /* 根据degree的大小找到当前节点的父节点 */
        for (const neighbour of neg) {
            if (adj.has(neighbour) && adj.get(neighbour).size < parentDegree && adj.get(neighbour).size >= currDegree) {
                parentNode = neighbour;
                parentDegree = adj.get(neighbour).size;
            }
        }
        if (parentNode === -1) {
            return 0;
        }
        /* 检测父节点的集合是否包含所有的孩子节点 */
        for (const neighbour of neg) {
            if (neighbour === parentNode) {
                continue;
            }
            if (!adj.get(parentNode).has(neighbour)) {
                return 0;
            }
        }
        if (parentDegree === currDegree) {
            res = 2;
        }
    }
    return res;
};