【力扣】1359. 有效的快递序列数目

以下为力扣官方思路,以及本人代码

1359. 有效的快递序列数目

  • 题目
  • 示例1
  • 示例2
  • 示例3
  • 提示
  • 官方思路
    • 思路一 递推法
      • 代码
      • 复杂度分析
    • 思路二 整体法
      • 复杂度分析

题目

给你 n 笔订单,每笔订单都需要快递服务。
请你统计所有有效的 收件/配送 序列的数目,确保第 i 个物品的配送服务 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

官方思路

思路一 递推法

设 f[i] 表示订单数量为 i 时的序列数目,由于 f[i] 包含了 i 份订单,我们可以将其拆分为前 i-1 份订单(编号为 1,2,……,i-1)与 1 份额外的订单(编号为 i)。

对于 f[i-1],它的长度为 (i-1)*2,我们只需要在这个序列中加上第 i 份订单,就可以得到一条订单数量为 i 的序列。

由于第 i 份订单包含的 Pi​ 在序列中必须出现在 Di 之前,因此有两种将第 i 份订单加入 f[i-1] 中,具体如下:

  • 第一种:Pi 和 Di 被添加到了序列中连续的位置。由于序列的长度为 (i - 1) * 2,那么可以添加的位置为序列的长度加 1,即 2(i−1)+1=2i−1。
  • 第二种:Pi 和 Di 被添加到了序列中不连续的位置。那么就相当于在 2*i-1 个位置中选择两个不同的位置,前者用作添加 Pi,后者用作添加 Di​,方法数为组合数 C(2, 2i−1)=(2i−1)(i−1)。

将这两类的方法数量相加,就可以得到:对于一个固定的包含前 i-1 份订单的固定序列,用 (2i−1)*i 种方法可以加入第 i 份订单。易得递推公式 f[i] = (2i-1)*i *f[i-1]。

代码

class Solution {
    public int countOrders(int n) {
        if(n==1)
            return 1;
        else
            return (int)(((long)(countOrders(n-1)) *(2*n-1)*n) % 1000000007);	// countOrders(n-1) 不可以是 int 类型,不然 n-1>=8 时,该值会因为大数值被强制转换成 int 而出错
    }
}

复杂度分析

  • 时间复杂度:O(n)。
  • 空间复杂度:O(1)。

思路二 整体法

除了递推法之外,我们也可以从整体进行考虑,直接得到 f[i] 的通项公式。

假设现在没有 Di 必须在 Pi 之后的要求,那么 f[i]=(2i)!。这是因为我们可以将 P1,D1,P2,D2,⋯ ,Pi,Di 的任意一个排列作为答案,排列的数目为 (2i)!,我们称这些排列为初始排列。

在加上了「Di 必须在 Pi 之后」的要求后,有一些初始排列就不能作为答案了。但是我们可以对它们进行调整,使它们变成满足要求的排列。具体地,对于任意一个初始排列,如果第 j 个物品的 Dj 出现在 Pj 之前,那么我们就把 Dj 和 Pj 交换顺序。在对 j=1, 2, …, i 全部判断一遍之后,我们就可以得到一个满足要求的排列了。

然而这样会导致重复计数,这是因为不同的初始排列在交换顺序之后会得到相同的排列,例如当 i=2 时,初始排列(D1, D2, P1, P2)和(D1, P2, P1, D2)在交换后都会得到相同的排列(P1, P2, D1, D2)。

如何去除重复计数呢?我们可以进行逆向思考,反过来计算一个排列可以从多少个初始排列得来。显然,对于排列中的 i 对 Pj 和 Dj,我们将其中任意数量的对交换顺序,都可以得到一个不同的初始排列。交换顺序的选择有 2i 种(每一对 Pj 和 Dj 交换或不交换),因此一个排列对应着 2i 个初始排列,即排列的数目为:f[i] = (2i)! / 2i

这样就得到了 f[i] 的通项公式。由于通项公式中包含除法,在取模的意义下不好直接计算,我们可以将分母 2i 拆分成 i 个 2,与分子阶乘中的 i 个偶数相除,得到 f[i] = i!(2i-1)!!,其中 (2i-1)!! = 1 · 3 · … ·(2i-1),其与思路一中的递推式是一致的,可以直接使用思路一的代码。

复杂度分析

  • 时间复杂度:O(n)。
  • 空间复杂度:O(1)。

你可能感兴趣的:(力扣,java,leetcode)