你的音乐播放器里有 n 首不同的歌,在旅途中,你计划听 goal 首歌(不一定不同,即,允许歌曲重复)。你将会按如下规则创建播放列表: 每首歌 至少播放一次 。 一首歌只有在其他 k 首歌播放完之后才能再次播放。 给你 n、goal 和 k ,返回可以满足要求的播放列表的数量。由于答案可能非常大,请返回对 1e9 + 7 取余 的结果。
示例 1: 输入:n = 3, goal = 3, k = 1
输出:6
解释:有 6 种可能的播放列表。[1, 2, 3],[1, 3, 2],[2, 1, 3],[2, 3, 1],[3, 1, 2],[3, 2, 1] 。
程序的开头已经给出:
class Solution:
def numMusicPlaylists(self, n: int, goal: int, k: int) -> int:
要解决这个问题,我们可以使用动态规划(Dynamic Programming, DP)来计算可以满足条件的播放列表数量。问题的关键是如何构建一个播放列表,使得每首歌至少播放一次,并且在重复播放之前至少有 k 首其他歌曲播放过。
解题思路:
我们定义一个二维数组 dp[i][j],表示用 i 首不同的歌构成一个长度为 j 的播放列表的数量。我们的目标是计算 dp[n][goal]。
转移方程:
如果我们选择一首新的歌曲来扩展当前的播放列表(长度增加1),那么新的歌曲是从 n - (i - 1) 首未使用的歌曲中选择。即,我们有 dp[i-1][j-1] * (n - (i - 1)) 种方式。
如果我们选择一首已经在播放列表中使用过的歌曲来扩展当前的播放列表(长度增加1),那么我们必须确保在前 k 首歌曲之后才能再次播放同一首歌曲。因此,这里有 dp[i][j-1] * (i - k) 种方式。
所以,转移方程为:
dp[i][j] = dp[i-1][j-1] * (n - (i - 1)) + dp[i][j-1] * (i - k)
其中,dp[i][j] 表示用 i 首不同的歌构成一个长度为 j 的播放列表的数量。
边界条件
dp[0][0] = 1:没有歌曲和播放列表时,有一种方法(即什么都不做)。
class Solution:
def numMusicPlaylists(self, n: int, goal: int, k: int) -> int:
MOD = int(1e9 + 7)
# 初始化 dp 数组
dp = [[0] * (goal + 1) for _ in range(n + 1)]
dp[0][0] = 1 # 边界条件
# 填充 dp 数组
for i in range(1, n + 1):
for j in range(1, goal + 1):
# 选择一个新的歌曲加入到播放列表的末尾
dp[i][j] += dp[i-1][j-1] * (n - (i - 1))
dp[i][j] %= MOD # 对结果取模
# 选择一个已经播放过的歌曲(但是要满足 k 首不同歌曲的间隔条件)
if i > k:
dp[i][j] += dp[i][j-1] * (i - k)
dp[i][j] %= MOD # 对结果取模
return dp[n][goal]
# 示例测试
solution = Solution()
print(solution.numMusicPlaylists(3, 3, 1)) # 输出: 6