Leetcode 第 374 场双周赛 Problem D 100146. 统计感冒序列的数目(组合数学+阶乘+逆元) 题目 给你一个整数 n 和一个下标从 0 开始的整数数组 sick ,数组按 升序 排序。 有 n 位小朋友站成一排,按顺序编号为 0 到 n - 1 。数组 sick 包含一开始得了感冒的小朋友的位置。如果位置为 i 的小朋友得了感冒,他会传染给下标为 i - 1 或者 i + 1 的小朋友,前提 是被传染的小朋友存在且还没有得感冒。每一秒中, 至多一位 还没感冒的小朋友会被传染。 经过有限的秒数后,队列中所有小朋友都会感冒。感冒序列 指的是 所有 一开始没有感冒的小朋友最后得感冒的顺序序列。请你返回所有感冒序列的数目。 由于答案可能很大,请你将答案对 10 ^ 9 + 7 取余后返回。 注意,感冒序列 不 包含一开始就得了感冒的小朋友的下标。 2 <= n <= 10 ^ 5 1 <= sick.length <= n - 1 0 <= sick[i] <= n - 1 sick 按升序排列。 示例 示例 1: 输入:n = 5, sick = [0,4] 输出:4 感冒序列 [1,2,3] ,被传染的顺序:[0 ,1,2,3,4 ] => [0,1 ,2,3,4 ] => [0,1,2 ,3,4 ] => [0,1,2,3,4 ] 感冒序列 [1,3,2] ,被传染的顺序:[0 ,1,2,3,4 ] => [0,1 ,2,3,4 ] => [0,1 ,2,3,4 ] => [0,1,2,3,4 ] 感冒序列 [3,1,2] ,被传染的顺序:[0 ,1,2,3,4 ] => [0 ,1,2,3,4 ] => [0,1 ,2,3,4 ] => [0,1,2,3,4 ] 感冒序列 [3,2,1] ,被传染的顺序:[0 ,1,2,3,4 ] => [0 ,1,2,3,4 ] => [0 ,1,2,3,4 ] => [0,1,2,3,4 ] 示例 2: 输入:n = 4, sick = [1] 输出:3 感冒序列 [0,2,3] ,被传染的顺序:[0,1 ,2,3] => [0,1 ,2,3] => [0,1,2 ,3] => [0,1,2,3 ] 感冒序列 [2,0,3] ,被传染的顺序:[0,1 ,2,3] => [0,1,2 ,3] => [0,1,2 ,3] => [0,1,2,3 ] 感冒序列 [2,3,0] ,被传染的顺序:[0,1 ,2,3] => [0,1,2 ,3] => [0,1,2,3 ] => [0,1,2,3 ] 解法 组合数学+阶乘+逆元: 第 1 步: 分析题目易得每秒都 有且仅仅有一位未感冒者 被传染,且他的左或右一定有感冒的人, 然后观察示例可以想到:未感冒者会组成 k 个连续区间,除了第一段以及看最后一段区间,中间区间全是左右均有感冒 第 2 步: 因为已感冒者无法被影响,因此先在区间内部考虑,再思考区间与区间的关系, 第 3 步: 区间内部的方案数: 第一段或者最后一段仅有 1 种方案数,从右到左与从左到右 中间区间 pre[i] 个人、方案数为:2 ^ (pre[i]-1) 暴力枚举也可 DP 也行:dp[i] 代表 i 个人方案数由 i-1 个人方案数选 首/尾,且最后一个人无法选择:dp[i] = dp[i-1] * 2 第 4 步 :每段区间方案数相乘即 总方案数 :1 * 2 ^(per[1]-1 + … + per[k-1]-1)* 1 但这仅是以整个区间来考虑的,如果形如:pre[0] 中选第一个、pre[2] 中选最后一个、pre[1] 中选第一个… 我们将其称为排列数, 第 5 步 :排序数:sum!/(per0!*per1!*…*perk!) 设 pre[i] 总和为 sum,每个区间 per[i] 只有 1 种排序方式(从前到后顺序放入),而区间内部如何选由方案数决定, 接着我们又两种思考方式(公式化简后可以互相转化) 先不管 per 顺序、仅看 sum 的总排序数为 sum!,每种 per 的总排序数 per! 变成 1 种排序方式,结果就是:sum!/ (per0! * per1! * … * perk!) 从 s 个空位中找到 per[0] 个位置顺序放入、结果为 C(s, per[0]),接着从 s-per[0] 个空位找 per[1] 个位置顺序放入、结果为 C(s-per[0], per[1]) … , 总结果就是 C(s, per[0])* C(s-per[0], per[1]) * … * C(s-per[0]-…-per[k-1], per[k])(可以化简为 sum!/ (per[0]! * per[1]! * … * per[k]!)) 第 6 步: 结果就是:每种区间内部方案数 * 区间之间的排列数 代码
public int numberOfSequence ( int n, int [ ] sick) { long res = 1 ; long perTotal = FAC [ n - sick. length] ; int per = sick[ 0 ] ; perTotal *= INV_FAC [ per] ; perTotal %= MOD ; int ansPow = 0 ; for ( int i = 1 ; i < sick. length; i++ ) { per = sick[ i] - sick[ i - 1 ] - 1 ; perTotal *= INV_FAC [ per] ; perTotal %= MOD ; if ( per > 0 ) { ansPow += per - 1 ; } } per = n - sick[ sick. length - 1 ] - 1 ; perTotal *= INV_FAC [ per] ; perTotal %= MOD ; res = qPow ( 2 , ansPow, MOD ) * perTotal % MOD ; return ( int ) ( res) ; } private static final int MOD = ( int ) 1e9 + 7 ; private static final int MX = 100_000 ; private static final long [ ] FAC = new long [ MX ] ; private static final long [ ] INV_FAC = new long [ MX ] ; static { FAC [ 0 ] = 1 ; for ( int i = 1 ; i < MX ; i++ ) { FAC [ i] = FAC [ i - 1 ] * i % MOD ; } INV_FAC [ MX - 1 ] = qPow ( FAC [ MX - 1 ] , MOD - 2 , MOD ) ; for ( int i = MX - 1 ; i > 0 ; i-- ) { INV_FAC [ i - 1 ] = INV_FAC [ i] * i % MOD ; } } private static long qPow ( long value, long pow, long mod) { long res = 1 ; while ( pow > 0 ) { if ( ( pow & 1 ) == 1 ) { res *= value; res %= mod; } value *= value; value %= mod; pow >>= 1 ; } return res; }