Description

給定 n 個訂單,每個有取件 (Pi) 和送件 (Di)。取件必須在送件之前。求所有合法排列的數量,結果對 10^9 + 7 取模。

Example:

Input: n = 2 Output: 6

Intuition#

增量法:已排好 i-1 個訂單後,第 i 個訂單的 (Pi, Di) 有 (2i-1) * 2i / 2 種插入方式。

  • 已排好 i-1 個訂單,序列長度為 2(i-1)
  • 插入第 i 個訂單的 Pi 和 Di,共有 2i-1 個空位放 Pi
  • Pi 放好後,Di 必須在 Pi 之後,有 (2i-1) 到 1 個位置可選
  • 每一步的方案數 = (2i-1) * i = (2i-1) * 2i / 2

Approaches#

1: DP#

  • 概念: dp[i] = dp[i-1] * (2i-1) * i,逐步累乘
  • 時間複雜度: O(n)
  • 空間複雜度: O(n)
class Solution {
    fun countOrders(n: Int): Int {
        val MOD = 1_000_000_007L
        val dp = LongArray(n + 1)
        dp[1] = 1
        for (i in 2..n) {
            val slots = 2L * i - 1
            dp[i] = dp[i - 1] * slots % MOD * i % MOD
        }
        return dp[n].toInt()
    }
}

⭐2: Math (Rolling Multiplication)#

  • 概念: 直接用變數累乘,不需陣列
  • 時間複雜度: O(n)
  • 空間複雜度: O(1)
class Solution {
    fun countOrders(n: Int): Int {
        val MOD = 1_000_000_007L
        var result = 1L
        for (i in 2..n) {
            // 插入第 i 個訂單:Pi 有 (2i-1) 個空位
            // Di 必須在 Pi 之後,平均有 i 個位置
            // 方案數 = (2i-1) * i
            result = result * (2L * i - 1) % MOD * i % MOD
        }
        return result.toInt()
    }
}

🔑 Takeaways#

  • Pattern: 組合計數 DP — 增量插入法,每步計算新元素的插入方案數
  • 關鍵技巧: 第 i 個訂單插入時,Pi 有 2i-1 個間隔可放,Di 必須在 Pi 後面所以有 (2i-1+1)/2 = i 個有效位置(C(2i-1, 1) 選 Pi 位置,再從剩餘選 Di)