LeetCode 920. 播放列表的数量
题目描述
思路分析
解法一:动态规划(推荐)
核心思路:
- 定义
dp[i][j]为恰好听了i首歌且列表中包含j首不同歌曲的播放列表数量- 转移方程分两种情况:新加一首从未听过的歌
dp[i][j] += dp[i-1][j-1] * (n - (j-1));重复一首之前听过的歌,但不是最近k首,dp[i][j] += dp[i-1][j] * max(0, j - k)- 初始状态
dp[0][0] = 1,最终答案为dp[goal][n]- 使用滚动数组将空间优化至 O(n)
复杂度分析:
- 时间复杂度:O(goal × n),其中 goal 为播放列表长度,n 为歌曲总数
- 空间复杂度:O(n),滚动数组只保留一行状态
class Solution {
public int numMusicPlaylists(int n, int goal, int k) {
long mod = 1_000_000_007L;
// dp[j] 表示当前播放列表中恰好包含 j 首不同歌曲的方案数
long[] dp = new long[n + 1];
dp[0] = 1;
for (int i = 1; i <= goal; i++) {
// 从后向前遍历,避免覆盖
long[] newDp = new long[n + 1];
for (int j = 1; j <= Math.min(i, n); j++) {
// 情况一:第 i 首是新歌,从 n-(j-1) 首中选
newDp[j] = (newDp[j] + dp[j - 1] * (n - (j - 1))) % mod;
// 情况二:第 i 首是旧歌,但不在最近 k 首中
if (j > k) {
newDp[j] = (newDp[j] + dp[j] * (j - k)) % mod;
}
}
dp = newDp;
}
return (int) dp[n];
}
}
func numMusicPlaylists(n int, goal int, k int) int {
const mod = 1_000_000_007
// dp[j] 表示播放列表中恰好包含 j 首不同歌曲的方案数
dp := make([]int, n+1)
dp[0] = 1
for i := 1; i <= goal; i++ {
newDp := make([]int, n+1)
for j := 1; j <= min920(i, n); j++ {
// 情况一:第 i 首是新歌,从 n-(j-1) 首未播放过的歌中选
newDp[j] = (newDp[j] + dp[j-1]*(n-(j-1))) % mod
// 情况二:第 i 首是旧歌,但不在最近 k 首中
if j > k {
newDp[j] = (newDp[j] + dp[j]*(j-k)) % mod
}
}
dp = newDp
}
return dp[n]
}
func min920(a, b int) int {
if a < b {
return a
}
return b
}
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 518. 零钱兑换 II | 中等 | 动态规划、组合计数 |
| 377. 组合总和 Ⅳ | 中等 | 动态规划、排列计数 |
| 1359. 有效的快递序列数目 | 困难 | 动态规划、数学 |
| 62. 不同路径 | 中等 | 动态规划、组合数学 |
| 96. 不同的二叉搜索树 | 中等 | 动态规划、卡特兰数 |
| 1866. 恰好有 K 根木棍可以看到的排列数目 | 困难 | 动态规划、第一类斯特林数 |
本博客所有文章除特别声明外,均采用
CC BY-NC-SA 4.0
许可协议,转载请注明出处!