腾讯笔试:n人组队问题
题目
某部门共有 n n n个人,现在需要从所有人中选出任意数量(大于0)的人组成一个小队,并从中选出一个队长,那么一共有多少种组队方式?其中 1 ≤ n ≤ 1 0 9 1≤n≤10^9 1≤n≤109,由于组队方式的数目可能会很大,请将结果对 ( 1 0 9 + 7 ) (10^9+7) (109+7)取余后输出。
示例
输入: 2
输出: 4
解释
如果将2个人记为“1”和“2”,则共有4种组队方式(其中上方带点标记的为队长):
{
1
˙
\dot{1}
1˙ ̇},{
2
˙
\dot{2}
2˙},{
1
˙
\dot{1}
1˙, 2},{1,
2
˙
\dot{2}
2˙}
解法
思路1
按排列组合的方式直接解,队伍人数为
i
(
1
≤
i
≤
n
)
i(1\leq i \leq n)
i(1≤i≤n)的情况有
C
n
i
C_n^i
Cni种,其中每一种又对应
i
i
i种情况(每个人都可以被选为队长),则总共的组队方式数目为
∑
i
=
1
n
i
⋅
C
n
i
\sum_{i=1}^{n}i \cdot C_n^i
∑i=1ni⋅Cni
其中
C
n
i
C_n^i
Cni表示组合数,即在
n
n
n个对象中选择
i
i
i个对象的选择方式的数目。对应的代码为
// 思路1
def fnc(a,b):
if b == 0:
return a
elif b == a:
return 1
ans = 1
num, den = a, b
for _ in range(b):
ans *= num / den
num -= 1
den -= 1
return ans
n = int(input())
MOD = int(1e9 + 7)
ans = 0
for i in range(1,n+1):
ans += i * fnc(n,i) % MOD
print(int(ans))
经测试,这种方式不是溢出就是超时,无法通过。
思路2
最后组成的队伍中必然有一个队长,这个队长可能是
n
n
n个人中的任意一个,那么队长是任意一个人的组队方式相等,也就是说总的组队方式数必然是一个数量的
n
n
n倍,将那个数目记为
x
x
x(总数目为
n
x
nx
nx),接下来考虑
x
x
x的值;
已经有一个人被选为队长了,其余的人构成一个集合,那么在这个集合中任选一个子集即可与队长组成队伍(即便是空集也可以),由于剩下的一共有
(
n
−
1
)
(n-1)
(n−1)个人,则
x
x
x便等于子集的总数
2
n
−
1
2^{n-1}
2n−1,于是
n
x
=
n
⋅
2
(
n
−
1
)
nx = n \cdot 2^{(n-1)}
nx=n⋅2(n−1)
这样,我们直接求出了解析解。
观察这个表达式以及
n
n
n的取值范围,显然,接下来我们将面临2个问题:
- 若直接使用幂函数计算,则必然溢出;
- 考虑同余定理,使用迭代的方式对进行运算且在每次迭代时均进行取余运算,算法复杂度为 O ( n ) O(n) O(n),必然超时。
可以对 2 ( n − 1 ) 2^{(n-1)} 2(n−1)不断进行二分拆分,最终达到 O ( O( O(log ( n ) ) (n)) (n))的复杂度;在每次拆分时都进行一次取余运算,以避免溢出。前者解决了超时问题,后者解决了溢出问题。最终代码
// 思路2
def power_mod(n, MOD):
if n == 1:
return 2
elif n % 2 == 0:
return (power_mod(n / 2, MOD) ** 2) % MOD
else:
return power_mod(n // 2, MOD) ** 2 * 2 % MOD
N = int(input())
MOD = int(1e9 + 7)
pm = power_mod(N - 1, MOD)
res = N * pm % MOD
print(res)
经测试,即使在笔记本电脑上,对于 n = 1 0 9 n=10^9 n=109的极限情况也可以瞬间算出结果。