偶数 个人站成一个圆,总人数为 num_people
。每个人与除自己外的一个人握手,所以总共会有 num_people / 2
次握手。
将握手的人之间连线,请你返回连线不会相交的握手方案数。
由于结果可能会很大,请你返回答案 模 10^9+7 后的结果。
示例 1:
输入:num_people = 2
输出:1
示例 2:
输入:num_people = 4
输出:2
解释:总共有两种方案,第一种方案是 [(1,2),(3,4)] ,第二种方案是 [(2,3),(4,1)] 。
示例 3:
输入:num_people = 6
输出:5
示例 4:
输入:num_people = 8
输出:14
提示:
2 <= num_people <= 1000
num_people % 2 == 0
解题思路
对于这种结果非常大的问题一定是采用分治策略。我们观察上面两张图,不难发现这样的结论,当i
和j
连接的时候,(i,j)
构成了一个子问题,[1,i)&&(j,n]
构成了一个子问题。
而此时的结果两个子问题结果的乘积,不同的连接方式对应不同的子问题,将所有的连接方式对应的子问题乘积相加就是最后的结果。我们定义函数 f ( i ) f(i) f(i)表示 i i i个人时候的结果,那么
- f ( i ) = ∑ 1 n f ( i − 1 ) ∗ f ( n − i − 1 ) f(i)=\sum_{1}^{n}f(i-1)*f(n - i-1) f(i)=∑1nf(i−1)∗f(n−i−1)
最后需要思考边界条件,当num=1
的时候,结果是0
。需要注意的是,当num=0
的时候,结果是1
(应该最后的结果是两个数的乘积,此时相当于最后结果是另一个数)。
from functools import lru_cache
class Solution:
def numberOfWays(self, n: int) -> int:
mod = 10**9 + 7
@lru_cache(None)
def dfs(k):
if k == 0:
return 1
if k == 1:
return 0
res = 0
for i in range(1, k):
res = (res + dfs(i - 1) * dfs(k - i - 1) % mod) % mod
return res
return dfs(n)
题目中的一个条件我们一直没有使用,总共有偶数个人数。所以可以这样写
from functools import lru_cache
class Solution:
def numberOfWays(self, n: int) -> int:
mod = 10**9 + 7
@lru_cache(None)
def dfs(k):
if k == 0:
return 1
res = 0
for i in range(1, k, 2):
res = (res + dfs(i - 1) * dfs(k - i - 1) % mod) % mod
return res
return dfs(n)
更简洁的写法
from functools import lru_cache
class Solution:
@lru_cache(None)
def numberOfWays(self, n: int) -> int:
return sum(self.numberOfWays(i - 1) * self.numberOfWays(n - 1 - i) for i in range(1, n, 2))%(10**9 + 7) if n else 1
实际上上面的计算过程可以变换为
- f ( 2 n ) = f ( 0 ) ∗ f ( 2 n − 2 ) + . . + f ( 2 n − 2 ) ∗ f ( 0 ) f(2n)=f(0)*f(2n-2)+..+f(2n-2)*f(0) f(2n)=f(0)∗f(2n−2)+..+f(2n−2)∗f(0)
这实际上在算卡特兰数,所以可以将上面的计算方式转换为
- f ( n ) = f ( 0 ) ∗ f ( n − 1 ) + . . + f ( n − 1 ) ∗ f ( 0 ) = C n n / 2 / ( n / 2 + 1 ) f(n)=f(0)*f(n-1)+..+f(n-1)*f(0)=C_n^{n/2}/(n/2+1) f(n)=f(0)∗f(n−1)+..+f(n−1)∗f(0)=Cnn/2/(n/2+1)
class Solution:
def numberOfWays(self, n: int) -> int:
res = 1
for i in range(1, n // 2 + 1):
res *= n - i + 1
res //= i
return res // (n // 2 + 1) % (10**9 + 7)
在使用cpp
等语言计算的时候,由于除法取摸运算不同余(根据同余定理),所以我们需要先计算逆元(这里使用逆元线性筛来计算)。
class Solution:
def numberOfWays(self, n: int) -> int:
inv = [0] * (n // 2 + 2)
inv[1], mod, res = 1, 10**9 + 7, 1
for i in range(2, n//2 + 2):
inv[i] = mod - mod//i * inv[mod%i]%mod
for i in range(1, n//2 + 1):
res = res * (i + n//2) % mod
res = res * inv[i] % mod
return res * inv[n//2 + 1] % mod
reference:
https://leetcode.com/problems/handshakes-that-dont-cross/discuss/430539/JavaC%2B%2BPython-DP-and-O(N)
我将该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!