Description
你有
n首不同的歌,要建立一個長度為goal的播放清單。規則:每首歌至少播放一次;一首歌再次播放前,必須先播放k首其他歌。回傳可能的播放清單數量(對 10^9+7 取餘)。
Example:
Input: n = 3, goal = 3, k = 1 Output: 6
Intuition#
- 第
i首歌可以是新歌(有n - j + 1種選擇)或舊歌(有max(0, j - k)種選擇,因為最近 k 首不能重複) - 轉移方程:
dp[i][j] = dp[i-1][j-1] * (n-j+1) + dp[i-1][j] * max(j-k, 0)
Approaches#
1: 2D DP#
- 概念: 建立
dp[goal+1][n+1]表格,按轉移方程填表。 - 時間複雜度:
O(goal * n) - 空間複雜度:
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(空間優化)#
- 概念: 由於
dp[i][j]只依賴dp[i-1][j-1]和dp[i-1][j],可壓縮為一維陣列,逆序更新。 - 時間複雜度:
O(goal * n) - 空間複雜度:
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#
- Pattern: 計數型 DP — 新歌 vs 舊歌的選擇
- 關鍵技巧: 「新歌」有
n-j+1種選擇,「舊歌」受 k 間隔限制只有max(j-k, 0)種;逆序更新壓縮空間