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 原题链接和其他优选题解。

相关文章
|
15天前
|
数据处理
重复值的判断标准是否可以根据具体业务需求进行调整?
重复值的判断标准需要紧密结合具体的业务需求进行灵活调整,这样才能确保数据处理的准确性和有效性,为业务决策提供可靠的数据支持。
50 10
|
2月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
47 3
|
6月前
|
人工智能
技术心得记录:关于自补图的认识和构造(无证明)
技术心得记录:关于自补图的认识和构造(无证明)
173 0
|
7月前
|
数据采集 SQL 监控
分析重复数据通常涉及以下步骤,以确保对重复项的来源和性质有深入理解
【4月更文挑战第2天】分析重复数据通常涉及以下步骤,以确保对重复项的来源和性质有深入理解
66 1
|
JavaScript 安全 前端开发
修改MD5值:降低iOS应用程序关联性判定,减少拒绝风险
ios应用程序存储一些图片,资源,配置信息,甚至敏感数据如用户信息、证书、私钥等。这些数据怎么保护呢?可以使用iOS提供的Keychain来保护敏感数据,也可以使用加密技术,或者使用Ipa Guard 来弱化文件名称含义,增加破解难度。实现保护iOS app应用程序不被反编译、破解或篡改。
|
算法 安全 机器人
算法提高:计算几何基础 | 判断包含关系
计算几何是计算机科学的一个重要分支,主要研究几何形体的数学描述和计算机描述,在现代工程和数学领域,以及计算机辅助设计、地理信息系统、图形学、机器人技术、超大规模集成电路设计和统计等诸多领域都有重要的用途。在 ACM 竞赛中,出题相对独立,曾出现过与图论、动态规划相结合的题,大多数计算几何问题用程序实现都比较复杂。常用算法包括经典的凸包求解、离散化及扫描线算法、旋转卡壳、半平面交等。本文介绍计算几何常用算法——包含关系。
166 0
|
存储 资源调度 数据可视化
R问题|数值模拟流程记录和分享
R问题|数值模拟流程记录和分享
148 0
|
算法
一篇文章带你整体了解算法中的基本问题《查找》
查找 本章对算法中的基本问题--查找做了一个简要介绍,包含了一些基本算法思想以及评价,后续文章详细介绍一些算法,欢迎关注本系列。 可以转载,但请声明源链接:文章源链接justin3go.com(有些latex公式某些平台不能渲染可查看这个网站)
75 0
|
人工智能
LeetCode 1389. 按既定顺序创建目标数组
给你一个字符串 s,它由数字('0' - '9')和 '#' 组成。我们希望按下述规则将 s 映射为一些小写英文字符
85 0
|
Rust 自然语言处理 算法
【算法】1389. 按既定顺序创建目标数组(多语言实现)
给你两个整数数组 nums 和 index。你需要按照以下规则创建目标数组: 目标数组 target 最初为空。 按从左到右的顺序依次读取 nums[i] 和 index[i],在 target 数组中的下标 index[i] 处插入值 nums[i] 。 重复上一步,直到在 nums 和 index 中都没有要读取的元素。 请你返回目标数组。 题目保证数字插入位置总是存在。
【算法】1389. 按既定顺序创建目标数组(多语言实现)