LeetCode——1916. 统计为蚁群构筑房间的不同顺序(Count Ways to Build Rooms in an Ant Colony)[困难]——分析及代码(Java)

LeetCode——1916. 统计为蚁群构筑房间的不同顺序[Count Ways to Build Rooms in an Ant Colony][困难]——分析及代码[Java]

一、题目

你是一只蚂蚁,负责为蚁群构筑 n 间编号从 0 到 n-1 的新房间。给你一个 下标从 0 开始 且长度为 n 的整数数组 prevRoom 作为扩建计划。其中,prevRoom[i] 表示在构筑房间 i 之前,你必须先构筑房间 prevRoom[i] ,并且这两个房间必须 直接 相连。房间 0 已经构筑完成,所以 prevRoom[0] = -1 。扩建计划中还有一条硬性要求,在完成所有房间的构筑之后,从房间 0 可以访问到每个房间。

你一次只能构筑 一个 房间。你可以在 已经构筑好的 房间之间自由穿行,只要这些房间是 相连的 。如果房间 prevRoom[i] 已经构筑完成,那么你就可以构筑房间 i。

返回你构筑所有房间的 不同顺序的数目 。由于答案可能很大,请返回对 10^9 + 7 取余 的结果。

示例 1:

输入:prevRoom = [-1,0,1]
输出:1
解释:仅有一种方案可以完成所有房间的构筑:0 → 1 → 2

示例 2:

输入:prevRoom = [-1,0,0,1,2]
输出:6
解释:
有 6 种不同顺序:
0 → 1 → 3 → 2 → 4
0 → 2 → 4 → 1 → 3
0 → 1 → 2 → 3 → 4
0 → 1 → 2 → 4 → 3
0 → 2 → 1 → 3 → 4
0 → 2 → 1 → 4 → 3

提示:

  • n == prevRoom.length
  • 2 <= n <= 10^5
  • prevRoom[0] == -1
  • 对于所有的 1 <= i < n ,都有 0 <= prevRoom[i] < n
  • 题目保证所有房间都构筑完成后,从房间 0 可以访问到每个房间

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-ways-to-build-rooms-in-an-ant-colony
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二、分析及代码

1. 动态规划

(1)思路

对每个节点,可根据所有以其子节点为根的树的节点及排列数量,计算出以当前节点为根的树的节点及排列数量。
本题求解过程涉及较多前置知识点,包括排列组合、乘法逆元、快速乘方等,可参考官方题解

(2)代码

class Solution {
    int mod = (int)1e9 + 7;
    Map<Integer, List<Integer>> edges = new HashMap<>();
    int[] fac, inv;
    
    public int waysToBuildRooms(int[] prevRoom) {
        int n = prevRoom.length;

        //求阶乘数列及对应逆元
        this.fac = new int[n];//fac[i]=i!
        this.inv = new int[n];//inv[i]=i!^(-1)
        fac[0] = inv[0] = 1;
        for (int i = 1; i < n; i++) {
            fac[i] = (int)((long)fac[i - 1] * i % mod);
            inv[i] = quickmul(fac[i], mod - 2);//费马小定理, (fac[i]^(-1))%mod = (fac[i]^(mod-2))%mod
        }

        //记录各个节点与子节点之间的边
        for (int i = 1; i < n; i++) {
            if (edges.containsKey(prevRoom[i]) == false)
                edges.put(prevRoom[i], new ArrayList<Integer>());
            edges.get(prevRoom[i]).add(i);
        }

        //动态规划得到总体顺序数量
        return dfs(0)[1];      
    }

    //深度优先搜索,返回以当前节点为根的子树节点个数 及 内部排列数
    public int[] dfs(int node) {
        if (edges.containsKey(node) == false)//子节点,节点个数及内部排列数均为1
            return new int[]{1, 1};
        int count = 1, arr = 1;//子树的节点个数、内部排列数
        for (int v : edges.get(node)) {
            int[] ret = dfs(v);//递归得到子节点对应树的节点个数和排列数
            count += ret[0];
            arr = (int)((long)arr * ret[1] % mod * inv[ret[0]] % mod);
        }
        arr = (int)((long)arr * fac[count - 1] % mod);
        return new int[]{count, arr};
    }

    //快速计算x^y的乘方
    public int quickmul(int x, int y) {
        long ret = 1, cur = x;
        while (y > 0) {
            if ((y & 1) == 1)
                ret = ret * cur % mod;
            cur = cur * cur % mod;
            y >>= 1;
        }
        return (int)ret;
    }
}

(3)结果

执行用时 :684 ms,在所有 Java 提交中击败了 61.54% 的用户;
内存消耗 :576.8 MB,在所有 Java 提交中击败了 5.77% 的用户。

三、其他

暂无。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值