有效的快递序列数目

标签: 数学 动态规划 组合数学

难度: Hard

给你 n 笔订单,每笔订单都需要快递服务。

计算所有有效的 取货 / 交付 可能的顺序,使 delivery(i) 总是在 pickup(i) 之后。

由于答案可能很大,请返回答案对 10^9 + 7 取余的结果。

示例 1:

输入:n = 1
输出:1
解释:只有一种序列 (P1, D1),物品 1 的配送服务(D1)在物品 1 的收件服务(P1)后。

示例 2:

输入:n = 2
输出:6
解释:所有可能的序列包括:
(P1,P2,D1,D2),(P1,P2,D2,D1),(P1,D1,P2,D2),(P2,P1,D1,D2),(P2,P1,D2,D1) 和 (P2,D2,P1,D1)。
(P1,D2,P2,D1) 是一个无效的序列,因为物品 2 的收件服务(P2)不应在物品 2 的配送服务(D2)之后。

示例 3:

输入:n = 3
输出:90

提示:

  • 1 <= n <= 500

Submission

运行时间: 24 ms

内存: 0.0 MB

class Solution:
    def countOrders(self, n: int) -> int:
        res = 1
        MOD = 10**9+7
        for i in range(1,n+1):
            res*= (i*(2*i-1) % MOD)
            res = res % MOD
        return res % MOD
            

        
# 1 2 3 4
# 3 2 1

# 2n(2n-1)/2    (2n-2)(2n-3)/2

# n(n-1)/2 + (n-2)(n-3)/2

# n2-n + n2-5n+6  +   4 5

Explain

此题解利用数学组合的概念来计算所有有效的取货/交付序列数目。对于每个订单i,都有一个取货操作(Pi)和一个交付操作(Di)。每个订单的两个操作可以在序列中任意位置,但必须保证Di在Pi之后。因此,对于第i个订单,操作序列的位置可以看作是在已有的2*(i-1)个操作之后再添加两个新操作(Pi和Di),这两个新操作的相对位置有2个可能性(即Pi在前,Di在后)。而将这两个新操作插入到已有序列的任意位置的方法有2i-1个空隙可以选择。因此,对于第i个订单,其插入的排列方法总数为i*(2i-1)。故此,所有n个订单的有效序列数为连乘i*(2i-1)的结果,从i=1到i=n。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution:
    def countOrders(self, n: int) -> int:
        res = 1  # 初始化结果变量
        MOD = 10**9+7  # 定义取余的模数
        for i in range(1, n+1):  # 从1到n循环
            res *= (i * (2 * i - 1)) % MOD  # 每次循环计算i*(2i-1)并取余
            res = res % MOD  # 再次取余确保结果不会溢出
        return res % MOD  # 返回最终结果

Explore

在考虑第i个订单时,已经有i-1个订单的取货和交付操作,即存在2*(i-1)个操作。对于新的订单,我们需要在这些已有的操作中插入两个新操作:Pi和Di。在2*(i-1)个操作构成的序列中,存在2*(i-1)+1个可能的插入位置(每个操作之间以及序列的开始和结束)。但因为Pi和Di必须连续插入,且保持Pi在Di前的顺序,所以实际上我们只考虑插入第一个操作的位置,之后第二个操作自然跟随在第一个操作之后。因此,选择的插入空隙是2*(i-1)+1,即`2i-1`。

是的,对于每个新订单,虽然可以选择不同的插入位置,但一旦选择了插入的起始位置,两个操作Pi和Di的相对顺序必须保持不变,即Pi必须先于Di。这意味着在任何选择的插入位置,Pi总是位于Di之前。因此,操作顺序在选择的任何插入位置上都是恒定的。

这个表达式的推导基于每次插入新的订单操作的组合可能性。对于第i个订单,我们有`2i-1`个潜在的插入点。由于两个操作(取货Pi和交付Di)需要保持顺序,实际上我们是在这`2i-1`个潜在点中选择一个点插入Pi,然后Di紧跟在Pi后面。因此,每个新订单提供了`2i-1`种插入Pi的方式。同时,由于每个新订单都是独立的,之前的订单与这个新订单的插入方式无关,因此各个订单的插入方式可以独立计算并相乘。因此,总的方法数为从i=1到i=n的每个订单插入方法数的乘积,即连乘i*(2i-1)。

这样做的主要目的是为了防止在计算过程中因为数值过大而导致的整数溢出,以及保持最终结果的正确性。`MOD = 10**9+7`是一个大质数,通常用于计算结果的模运算来限制结果的大小。在每一步计算中,先计算`i * (2 * i - 1)`并立即对MOD取余,可以确保乘法结果不会超过整数范围。之后再将累乘结果对MOD取余,是为了保持累计结果不会溢出,并确保每一步的计算都在安全的数值范围内。