最近在刷题时遇到了一个有趣的排列问题,称为“好排列”。题目要求计算长度为 的好排列的数量,其中好排列的定义是相邻的两个数的乘积均为偶数。由于结果可能很大,需要对 取模。
解题思路
首先,我们需要理解什么情况下相邻两个数的乘积为偶数。只有当至少有一个数为偶数时,乘积才会是偶数。因此,要使所有相邻的数的乘积均为偶数,我们需要确保不存在两个奇数相邻的情况。
换句话说,我们需要将所有的奇数安排在排列中,使得它们之间至少隔一个偶数。基于这个思路,我们可以将问题转化为:
- 计算有多少种方法将奇数和偶数安排,使得没有两个奇数相邻。
- 分别排列奇数和偶数的顺序。
算法设计
1. 统计奇数和偶数的数量
- 计算奇数的数量 。
- 计算偶数的数量 。
2. 判断可行性
如果奇数的数量超过了偶数数量加一,即 ,那么无论如何排列,都无法避免两个奇数相邻的情况,此时答案为 0。
3. 计算排列方式
(a) 安排奇数的位置
我们有 个空位可供奇数放置(在偶数之间以及两端)。我们需要从中选择 个位置放置奇数。方法数为组合数 。
(b) 排列奇数和偶数的顺序
- 奇数可以有 种排列方式。
- 偶数可以有 种排列方式。
(c) 总的排列数
总的排列方式数为:
4. 模运算和逆元计算
由于需要对 取模,并且涉及到组合数和阶乘的计算,我们需要预处理阶乘和逆元,以高效计算组合数。
- 预处理阶乘 。
- 计算阶乘的逆元 ,利用费马小定理 。
代码实现
def solution(n: int) -> int:
mod = 10 ** 9 + 7
n_o = (n + 1) // 2 # 奇数的数量
n_e = n // 2 # 偶数的数量
if n_o > n_e + 1:
return 0
max_n = n_e + n_o
fact = [1] * (max_n + 1)
inv_fact = [1] * (max_n + 1)
for i in range(1, max_n + 1):
fact[i] = fact[i - 1] * i % mod
inv_fact[max_n] = pow(fact[max_n], mod - 2, mod)
for i in range(max_n - 1, -1, -1):
inv_fact[i] = inv_fact[i + 1] * (i + 1) % mod
def comb(n, k):
if k < 0 or k > n:
return 0
return fact[n] * inv_fact[k] % mod * inv_fact[n - k] % mod
ways = comb(n_e + 1, n_o)
ways = ways * fact[n_o] % mod
ways = ways * fact[n_e] % mod
return ways % mod
感悟
这道题考查了对排列组合的理解以及对模运算的掌握。在解决过程中,我体会到以下几点:
-
转化问题的能力:原始问题看似复杂,但通过分析,我们将其转化为避免奇数相邻的问题,大大简化了思考难度。
-
数学模型的建立:利用组合数学来计算位置安排和元素排列,使得问题有了明确的计算方法。
-
算法的优化:由于涉及大量的阶乘和组合数计算,直接计算会导致时间和空间复杂度过高。通过预处理阶乘和逆元,利用模运算性质,优化了计算效率。
-
细心和耐心:在处理边界条件和特殊情况(如奇数数量过多)时,需要仔细分析,避免出错。