开启我的LeetCode刷题日记:1719. 重构一棵树的方案数

162 阅读4分钟

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

编程世界总是离不了算法

最近在看框架源码时,会有很多算法的实现逻辑,有时候会感到吃力

于是决定蹭着假期,加强算法和数据结构相关的知识

那怎么提升呢?

其实我知道算法这东西没有捷径,多写多练才能提升,于是我开启我的LeetCode刷题之旅

第一阶段目标是:200道,每天12

为了不乱,本系列文章目录分为三部分:

  1. 今日题目:xxx
  2. 我的思路
  3. 代码实现

今天题目:1719. 重构一棵树的方案数

给你一个数组 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 。 一棵 有根树 指的是只有一个根节点的树,所有边都是从根往外的方向。

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

我的思路

根据题意,paris里的[x, y]可以表示x是y的祖先或y是x的祖先,我们可以称这种关系为关联关系 然后我们需要使用pairs里的节点生成一颗树,该树能满足paris里所有的关联关系

从树的叶子节点到根节点,节点高度越高,节点包含的子孙节点数量越多,所以关联关系最多的节点,最可能是根结点,且关联关系数量应该等于节点总数 - 1

我们可以先收集每个节点的关联关系,收集到的关联关系集合之后会称为关联集

比如:paris = [[1,2],[2,3]] 节点1的关联集为:(1, 2) 节点2的关联集为:(1, 2, 3) 节点3的关联集为:(2, 3)

那么,我们可以把能生成树的条件,转化成每个节点关联集能被满足 能被满足是指节点1的关联集里除了节点1,节点1都与其他节点存在关联关系

之后我们可以根据节点的关联集数量从大到小排序节点 再从关联集数量多的节点开始遍历,访问关联集里当前节点外的其他节点,判断当前的节点关联集是否包含其他节点的关联集 如果节点A关联集的包含节点B的关联集,那么说明节点A是节点B的祖先

当两节点的关联集相等时说明这两个节点的关联关系可以互换,那么生成树的方案数大于1

比如:paris = [[1,2]] 节点1的关联集为:(1, 2) 节点2的关联集为:(1, 2) 节点1和节点2能生成1 -> 2和2 -> 1

代码实现上我这里用了BigInt表示关联集,使用位运算方便关联集的相等和包含判断

代码实现

var checkWays = function (pairs) {
    // 生成每个节点的idx、关联集和关联集大小
    const map = Array.from({ length: 501 }, (_, i) => {
        const idx = 1n << BigInt(i);
        return [idx, idx, 0];
    });
    for (const [x, y] of pairs) {
        map[x][2]++;
        map[y][2]++;
        map[x][1] |= map[y][0];
        map[y][1] |= map[x][0];
    }
    // 根据关联集大小从大到小排序
    map.sort((a, b) => b[2] - a[2]);
    // 获取节点数量
    const n = map.findIndex(a => a[2] === 0);
    // 根节点的关联集大小要等于 n - 1
    if (map[0][2] !== n - 1) return 0;
    let res = 1;
    // 记录节点被满足的关联集
    const masks = Array.from({ length: n }, (_, i) => map[i][0]);
    for (let i = 0; i < n; i++) {
        // 节点的关联集已被满足,继续下一个
        if (masks[i] === map[i][1]) continue;
        for (let j = i + 1; j < n; j++) {
            // 节点i和节点j不存在关联关系,继续下一个
            if ((map[i][1] & map[j][0]) === 0n) continue;
            // 节点i和节点j关联集相等,说明节点i和节点j的关联关系能互换
            if (map[i][1] === map[j][1]) {
                res = 2;
            }
            // 节点i的关联集包含节点j,记录节点i和j被满足的关联集
            if ((map[i][1] | map[j][1]) === map[i][1]) {
                masks[i] |= map[j][1];
                masks[j] |= map[i][0];
            }
        }
        // 当前节点的关联集不被满足
        if (masks[i] !== map[i][1]) {
            return 0;
        }
    }
    return res;
};




总结

实现方式其实有很多,这里仅供参考~

由于刚开始刷题,也不知道从哪里刷好,如果前辈们有好的建议,希望不吝赐教,感谢🌹