1719. 重构一棵树的方案数 : 构造 + 验证(合法性 + 非唯一性)

简介: 1719. 重构一棵树的方案数 : 构造 + 验证(合法性 + 非唯一性)

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 1719. 重构一棵树的方案数 ,难度为 困难


Tag : 「树」


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


  • pairs 中没有重复元素
  • x_i < y_ixi<yi


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


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


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


请你返回:


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


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


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


示例 1:


网络异常,图片无法展示
|


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


示例 2:


网络异常,图片无法展示
|


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


示例 3:


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


提示:


  • 1 <= pairs.length <= 10^51<=pairs.length<=105
  • 1 <= xi < yi <= 5001<=xi<yi<=500
  • pairs 中的元素互不相同。


构造 + 验证(合法性 + 非唯一性)



这道题是名副其实的困难题,我最早的提交时间是去年 33 月,起初只能想到 O(n * m)O(nm) 的做法,对于一颗多叉树来说显然会 TLE,虽然到现在印象不深了,但如果不是之前做过,今天大概率会很晚才能发布题解。


该题突破口在于如何利用 pairs 构造出一种具体方案,然后辨别方案的合法性(是否返回 00)和方案中的某些点是否可相互替换(返回 11 还是 22)。


给定 pairs 数组为父子关系,对于 pairs[i] = (a,b)pairs[i]=(a,b) 而言,既可以是 aabb 祖宗节点,也可以是 bbaa 祖宗节点。


题目的「当且仅当」含义为所有的 pairs 在具体方案中均有体现,因此先考虑如何使用 pairs 构造出具体方案。


如果使用一棵合法树而言:每个子树的根节点在 pairs 中的出现数量满足大于等于其子节点在 pairs 中出现的数量(当某个根节点只有一个子节点时可取等号)。


利用此性质,虽然我们无法得知某个 pairs[i]pairs[i] 真实的父子关系,但统计每个点的「度数」可以作为决策根节点的依据


具体的,遍历 pairs 并统计所有点的度数,同时为了后续构造可以快速查询某两个点是否为父子关系,使用邻接矩阵 gg 存储关系,并使用 Set 统计点集。


之后将点按照「度数」降序,从前往后处理每个点,尝试构建具体方案(第一个点作为具体方案的根节点)。


对于每个非根节点 aa 而言,按道理我们可以将其添加到任意一个「度数不小于 cnt[i]cnt[i]」且「与其存在父子关系的」bb 中,但这样构造方式,只能确保能够得到「合法性」的结果,会为对于后续的「非唯一性」验证带来巨大困难。


因此这里尝试构造的关键点在于:我们为 aabb 的时候,应当找符合条件的、度数与 aa 相近的点。由于我们已经提前根据「度数」进行降序,这个找最优点的操作可从 aa 所在位置开始进行往回找,找到第一个满足「与 aa 存在父子关系」的点 bb 作为具体方案中 aa 的根节点。


这样的构造逻辑为后续的「非唯一性」验证带来的好处是:如果存在多个点能够相互交换位置的话,其在具体方案中必然为直接的父子关系,即我们能够通过判断 cnts[i]cnts[i]cnts[fa[i]]cnts[fa[i]] 是否相等,来得知在具体方案中点 iifa[i]fa[i] 能够交换,并且如果能够交换,具体方案的合法性不会发生改变。


一些细节:pairs 的数据范围为 10^4104,而后续的尝试构造,最坏情况下点数也在这个数量级上,为了防止在复杂度为 O(n^2)O(n2) 的尝试构造上耗费大量时间,可以增加 m < n - 1m<n1 的判断,在点数为 nn 的情况下,父子关系的最小值为 n - 1n1,当且仅当有一个根节点,其余均为叶子节点时取得,因此如果父子关系数量小于 n - 1n1,必然不为单棵子树,而是森林。


代码:


class Solution {
    int N = 510;
    int[] cnts = new int[N], fa = new int[N];
    boolean[][] g = new boolean[N][N];
    public int checkWays(int[][] pairs) {
        int m = pairs.length;
        Set<Integer> set = new HashSet<>();
        for (int[] p : pairs) {
            int a = p[0], b = p[1];
            g[a][b] = g[b][a] = true;
            cnts[a]++; cnts[b]++;
            set.add(a); set.add(b);
        }
        List<Integer> list = new ArrayList<>(set);
        Collections.sort(list, (a,b)->cnts[b]-cnts[a]);
        int n = list.size(), root = list.get(0);
        if (m < n - 1) return 0; // 森林
        fa[root] = -1;
        for (int i = 1; i < n; i++) {
            int a = list.get(i);
            boolean ok = false;
            for (int j = i - 1; j >= 0 && !ok; j--) {
                int b = list.get(j);
                if (g[a][b]) {
                    fa[a] = b;
                    ok = true;
                }
            }
            if (!ok) return 0;
        }
        int c = 0, ans = 1;
        for (int i : set) {
            int j = i;
            while (fa[j] != -1) {
                if (!g[i][fa[j]]) return 0;
                if (cnts[i] == cnts[fa[j]]) ans = 2;
                c++;
                j = fa[j];
            }
        }
        return c < m ? 0 : ans;
    }
}
复制代码


  • 时间复杂度:令 nnpairs 的长度,统计度数和点集复杂度为 O(n)O(n);最多有 2 * n2n 个点,将点根据度数进行排序复杂度为 O(n\log{n})O(nlogn);尝试根据度数构造树的复杂度为 O(n^2)O(n2);检验树的合法性复杂度为 O(n + m)O(n+m),其中 mm 是构造树的边数,数量级上与 nn 相等。整体复杂度为 O(n^2)O(n2)
  • 空间复杂度:O(n^2)O(n2)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.1719 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
设计模式 消息中间件 JavaScript
巧用『责任链模式』来优化 参数多重校验,非常优雅!
巧用『责任链模式』来优化 参数多重校验,非常优雅!
巧用『责任链模式』来优化 参数多重校验,非常优雅!
|
7天前
|
人工智能
技术心得记录:关于自补图的认识和构造(无证明)
技术心得记录:关于自补图的认识和构造(无证明)
10 0
|
2月前
|
数据采集 SQL 监控
分析重复数据通常涉及以下步骤,以确保对重复项的来源和性质有深入理解
【4月更文挑战第2天】分析重复数据通常涉及以下步骤,以确保对重复项的来源和性质有深入理解
15 1
|
2月前
|
存储 算法 数据管理
C++中利用随机策略优化二叉树操作效率的实现方法
C++中利用随机策略优化二叉树操作效率的实现方法
87 1
|
8月前
|
Java
策略枚举:消除在项目里大批量使用if-else的优雅姿势
可以替换大量的if-else语句,且具备较好的可读性与扩展性,同时能显得轻量化,我比较推荐使用策略枚举来消除if-else。
49 0
|
11月前
|
算法 安全 机器人
算法提高:计算几何基础 | 判断包含关系
计算几何是计算机科学的一个重要分支,主要研究几何形体的数学描述和计算机描述,在现代工程和数学领域,以及计算机辅助设计、地理信息系统、图形学、机器人技术、超大规模集成电路设计和统计等诸多领域都有重要的用途。在 ACM 竞赛中,出题相对独立,曾出现过与图论、动态规划相结合的题,大多数计算几何问题用程序实现都比较复杂。常用算法包括经典的凸包求解、离散化及扫描线算法、旋转卡壳、半平面交等。本文介绍计算几何常用算法——包含关系。
117 0
|
11月前
|
机器学习/深度学习 算法 计算机视觉
舌体胖瘦的自动分析-曲线拟合-或许是最简单判断舌形的方案(六)
舌体胖瘦的自动分析-曲线拟合-或许是最简单判断舌形的方案(六)
91 0
|
11月前
判断两棵树是否完全一致
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
95 0
|
11月前
|
算法 C语言 C++
【模拟】特别数的和、移动距离、连号区间、错误票据思路详解及代码实现
取出最后一位,然后将n除去最后一位,将刚刚取出的进行判定。
60 0
|
BI Python
条件独立5条重要性质及其证明
本文给出了条件独立5条重要性质及其证明
199 0
条件独立5条重要性质及其证明

热门文章

最新文章