Hard
题目描述
你的音乐播放器里有 n 首不同的歌曲。你想在旅途中听 goal 首歌(不一定不同)。为了避免无聊,你需要创建一个播放列表,满足以下条件:
- 每首歌至少播放一次
- 一首歌只有在播放了
k首其他歌曲后才能再次播放
给定 n、goal 和 k,返回你能创建的可能播放列表的数量。由于答案可能很大,请返回对 10^9 + 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]。
示例 2:
输入:n = 2, goal = 3, k = 0
输出:6
解释:有 6 种可能的播放列表:[1, 1, 2], [1, 2, 1], [2, 1, 1], [2, 2, 1], [2, 1, 2], 和 [1, 2, 2]。
示例 3:
输入:n = 2, goal = 3, k = 1
输出:2
解释:有 2 种可能的播放列表:[1, 2, 1] 和 [2, 1, 2]。
约束条件:
0 <= k < n <= goal <= 100
解题思路
这是一个经典的动态规划问题,需要仔细分析状态转移。
核心思路:
定义 dp[i][j] 表示播放了 i 首歌,其中有 j 首不同歌曲的方案数。
状态转移分析: 对于当前位置,我们有两种选择:
- 播放新歌:如果当前已有
j-1首不同歌曲,那么可以选择剩余的n-(j-1)首新歌中的任意一首 - 重复播放旧歌:如果当前已有
j首不同歌曲,且满足间隔要求(即j > k),那么可以选择之前播放过的j-k首歌
状态转移方程:
dp[i][j] = dp[i-1][j-1] * (n - (j-1)) + dp[i-1][j] * max(0, j-k)
边界条件:
dp[0][0] = 1(基础情况)- 其他
dp[0][j] = 0
优化思路: 由于每次状态转移只依赖前一行,可以使用滚动数组优化空间复杂度。但考虑到约束条件较小(goal ≤ 100),直接使用二维数组即可。
代码实现
class Solution {
public:
int numMusicPlaylists(int n, int goal, int k) {
const int MOD = 1e9 + 7;
vector<vector<long long>> dp(goal + 1, vector<long long>(n + 1, 0));
dp[0][0] = 1;
for (int i = 1; i <= goal; i++) {
for (int j = 1; j <= min(i, n); j++) {
// 播放新歌
dp[i][j] = (dp[i-1][j-1] * (n - (j - 1))) % MOD;
// 重复播放旧歌
if (j > k) {
dp[i][j] = (dp[i][j] + dp[i-1][j] * (j - k)) % MOD;
}
}
}
return dp[goal][n];
}
};
class Solution:
def numMusicPlaylists(self, n: int, goal: int, k: int) -> int:
MOD = 10**9 + 7
dp = [[0] * (n + 1) for _ in range(goal + 1)]
dp[0][0] = 1
for i in range(1, goal + 1):
for j in range(1, min(i, n) + 1):
# 播放新歌
dp[i][j] = (dp[i-1][j-1] * (n - (j - 1))) % MOD
# 重复播放旧歌
if j > k:
dp[i][j] = (dp[i][j] + dp[i-1][j] * (j - k)) % MOD
return dp[goal][n]
public class Solution {
public int NumMusicPlaylists(int n, int goal, int k) {
const int MOD = 1000000007;
long[,] dp = new long[goal + 1, n + 1];
dp[0, 0] = 1;
for (int i = 1; i <= goal; i++) {
for (int j = 1; j <= Math.Min(i, n); j++) {
// 播放新歌
dp[i, j] = (dp[i-1, j-1] * (n - (j - 1))) % MOD;
// 重复播放旧歌
if (j > k) {
dp[i, j] = (dp[i, j] + dp[i-1, j] * (j - k)) % MOD;
}
}
}
return (int)dp[goal, n];
}
}
var numMusicPlaylists = function(n, goal, k) {
const MOD = 1e9 + 7;
const dp = Array(goal + 1).fill(null).map(() => Array(n + 1).fill(0));
dp[0][0] = 1;
for (let i = 1; i <= goal; i++) {
for (let j = 1; j <= Math.min(i, n); j++) {
// 播放新歌
dp[i][j] = (dp[i-1][j-1] * (n - (j - 1))) % MOD;
// 重复播放旧歌
if (j > k) {
dp[i][j] = (dp[i][j] + dp[i-1][j] * (j - k)) % MOD;
}
}
}
return dp[goal][n];
};
复杂度分析
| 复杂度类型 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(goal × n) | 需要填充 goal × n 的二维数组 |
| 空间复杂度 | O(goal × n) | 存储动态规划状态的二维数组 |