Hard草稿★★★★★O(goal * n) 時間 · O(n) 空間
Dynamic ProgrammingCombinatorics
Patterns🔲 網格・雙序列 DP🧮 數學・組合
尚未複習過

解法已隱藏 — 先讀題目敘述、自己想想看,再點上方按鈕揭曉。

920Number of Music Playlists2-D DPHard2-D DP

你有 n 首不同的歌,要建立一個長度為 goal 的播放清單。規則:每首歌至少播放一次;一首歌再次播放前,必須先播放 k 首其他歌。回傳可能的播放清單數量(對 10^9+7 取餘)。

Example:

Input: n = 3, goal = 3, k = 1 Output: 6

Intuition

TIP

dp[i][j] 表示播放清單長度為 i、包含 j 首不同歌曲的方案數。

Approaches

1. 2D DP — O(goal * n) / O(goal * n)
  • Idea: 建立 dp[goal+1][n+1] 表格,按轉移方程填表。
  • Time: O(goal * n)
  • Space: O(goal * n)
class Solution {
    fun numMusicPlaylists(n: Int, goal: Int, k: Int): Int {
        val MOD = 1_000_000_007L
        val dp = Array(goal + 1) { LongArray(n + 1) }
        dp[0][0] = 1

        for (i in 1..goal) {
            for (j in 1..minOf(i, n)) {
                dp[i][j] = dp[i - 1][j - 1] * (n - j + 1) % MOD
                dp[i][j] = (dp[i][j] + dp[i - 1][j] * maxOf(j - k, 0) % MOD) % MOD
            }
        }
        return dp[goal][n].toInt()
    }
}
⭐ 2. 1D DP (Space Optimized) — O(goal * n) / O(n)
  • Idea: 由於 dp[i][j] 只依賴 dp[i-1][j-1]dp[i-1][j],可壓縮為一維陣列,逆序更新。
  • Time: O(goal * n)
  • Space: O(n)
class Solution {
    fun numMusicPlaylists(n: Int, goal: Int, k: Int): Int {
        val MOD = 1_000_000_007L
        val dp = LongArray(n + 1)
        dp[0] = 1

        for (i in 1..goal) {
            for (j in minOf(i, n) downTo 1) {
                dp[j] = (dp[j - 1] * (n - j + 1) % MOD + dp[j] * maxOf(j - k, 0) % MOD) % MOD
            }
            dp[0] = 0
        }
        return dp[n].toInt()
    }
}

🔑 Takeaways