leetcode
leetcode 1251 ~ 1300
有效的快递序列数目

有效的快递序列数目

难度:

标签:

题目描述

代码结果

运行时间: 24 ms, 内存: 0.0 MB


/*
 * 题目思路:
 * 给定 n 笔订单,每笔订单都需要快递服务。
 * 需要计算所有有效的取货/交付顺序,使得每个订单的交付在取货之后。
 * 本题使用 Java Stream 实现动态规划的思想。
 * 定义 dp 数组并使用 IntStream 来计算每一项的值。
 */

import java.util.stream.IntStream;

public class DeliveryOrderStream {
    public int countOrders(int n) {
        long MOD = 1000000007;
        long[] dp = new long[n + 1];
        dp[0] = 1;
        IntStream.rangeClosed(1, n).forEach(i -> dp[i] = dp[i - 1] * i % MOD * (2 * i - 1) % MOD);
        return (int) dp[n];
    }

    public static void main(String[] args) {
        DeliveryOrderStream order = new DeliveryOrderStream();
        System.out.println(order.countOrders(1)); // 输出 1
        System.out.println(order.countOrders(2)); // 输出 6
        System.out.println(order.countOrders(3)); // 输出 90
    }
}

解释

方法:

此题解利用数学组合的概念来计算所有有效的取货/交付序列数目。对于每个订单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)

代码细节讲解

🦆
对于每个订单的取货和交付操作来说,为什么选择的插入空隙数为`2i-1`而不是其他数目?
在考虑第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)的结果,这个表达式具体是如何推导出来的?
这个表达式的推导基于每次插入新的订单操作的组合可能性。对于第i个订单,我们有`2i-1`个潜在的插入点。由于两个操作(取货Pi和交付Di)需要保持顺序,实际上我们是在这`2i-1`个潜在点中选择一个点插入Pi,然后Di紧跟在Pi后面。因此,每个新订单提供了`2i-1`种插入Pi的方式。同时,由于每个新订单都是独立的,之前的订单与这个新订单的插入方式无关,因此各个订单的插入方式可以独立计算并相乘。因此,总的方法数为从i=1到i=n的每个订单插入方法数的乘积,即连乘i*(2i-1)。
🦆
代码中使用`res *= (i * (2 * i - 1)) % MOD`和`res = res % MOD`两次取余,这样做的目的是什么?
这样做的主要目的是为了防止在计算过程中因为数值过大而导致的整数溢出,以及保持最终结果的正确性。`MOD = 10**9+7`是一个大质数,通常用于计算结果的模运算来限制结果的大小。在每一步计算中,先计算`i * (2 * i - 1)`并立即对MOD取余,可以确保乘法结果不会超过整数范围。之后再将累乘结果对MOD取余,是为了保持累计结果不会溢出,并确保每一步的计算都在安全的数值范围内。

相关问题