Leetcode 1259:不相交的握手(超详细的解法!!!)

偶数 个人站成一个圆,总人数为 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

解题思路

对于这种结果非常大的问题一定是采用分治策略。我们观察上面两张图,不难发现这样的结论,当ij连接的时候,(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(i1)f(ni1)

最后需要思考边界条件,当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(2n2)+..+f(2n2)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(n1)+..+f(n1)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

如有问题,希望大家指出!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值