The "Number of Music Playlists" problem asks you to compute how many possible playlists of length goal
you can create using exactly n
different songs, where:
k
other songs have been played since it was last played.10^9 + 7
.Input:
n
: Total number of unique songsgoal
: The length of the playlistk
: The minimum number of different songs that must be played before a song can be repeatedgoal
that use every song at least once and obey the repeat rule.
At first glance, the problem seems to invite brute-force generation of all possible playlists and counting the valid ones. However, with constraints like n
up to 100 and goal
up to 100, this approach is computationally infeasible.
To optimize, we need a way to count playlists efficiently, ideally without generating each one. This leads us to dynamic programming (DP), where we can break the problem into smaller subproblems: for a given number of songs used and playlist length, how many ways can we extend the playlist?
The key insight is to model the playlist construction step by step, keeping track of how many unique songs have been used so far and how long the playlist is. We also need to enforce the rule that a song cannot repeat until at least k
other songs have been played.
We use dynamic programming to solve the problem efficiently. Let's break down the approach:
dp[i][j]
be the number of playlists of length i
that have exactly j
unique songs.dp[0][0] = 1
: One way to have a playlist of length 0 with 0 songs.n - j
options for this, so dp[i-1][j-1] * (n - (j-1))
.k
songs. There are max(j - k, 0)
options, so dp[i-1][j] * max(j - k, 0)
.dp[i][j] = dp[i-1][j-1] * (n - (j-1)) + dp[i-1][j] * max(j - k, 0)
.dp[goal][n]
, i.e., playlists of length goal
using all n
songs.10^9 + 7
.This DP approach allows us to count valid playlists efficiently without explicit enumeration.
Let's step through an example: n = 3
, goal = 3
, k = 1
.
dp[4][4]
(since we include 0).dp[0][0] = 1
.i = 1
(length 1):j = 1
: dp[1][1] = dp[0][0] * (3 - 0) = 1 * 3 = 3
(we can pick any of the 3 songs as the first song).
i = 2
(length 2):j = 1
: dp[2][1] = 0
(can't have only one unique song in 2 slots with k=1).j = 2
: dp[1][1] * (3-1) = 3*2 = 6
(add a new song to playlists of length 1 with 1 unique song).
i = 3
(length 3):j = 2
: dp[2][2] * max(2-1, 0) = 6*1 = 6
(repeat one of the 2 used songs, but not just played).j = 3
: dp[2][2] * (3-2) = 6*1 = 6
(add the last new song).
dp[3][3] = 6
. There are 6 valid playlists.This matches intuition: all permutations of 3 songs (since k=1, immediate repeats are forbidden) – there are 6 permutations.
goal
from n
songs, which is n^goal
possibilities. Exponential and intractable for large n
or goal
.dp[goal+1][n+1]
table. Each entry is computed in O(1) time.O(goal * n)
O(goal * n)
This is efficient and feasible for the given constraints.
The "Number of Music Playlists" problem challenges us to count how many playlists of a certain length use all songs at least once and respect a repetition gap rule. By thinking recursively and using dynamic programming, we count valid playlists by considering how many unique songs we've used and how long the playlist is, building up the solution step by step. This DP approach is both efficient and elegant, transforming a seemingly intractable enumeration problem into a manageable tabulation.
MOD = 10 ** 9 + 7
class Solution:
def numMusicPlaylists(self, n: int, goal: int, k: int) -> int:
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, n + 1):
# Add a new song
dp[i][j] += dp[i - 1][j - 1] * (n - (j - 1))
# Add a previously used song (not in last k)
if j > k:
dp[i][j] += dp[i - 1][j] * (j - k)
dp[i][j] %= MOD
return dp[goal][n]
#include <vector>
using namespace std;
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 <= n; ++j) {
// Add a new song
dp[i][j] = (dp[i][j] + dp[i-1][j-1] * (n - (j-1))) % MOD;
// Add a previously used song (not in last k)
if (j > k) {
dp[i][j] = (dp[i][j] + dp[i-1][j] * (j - k)) % MOD;
}
}
}
return (int)dp[goal][n];
}
};
class Solution {
public int numMusicPlaylists(int n, int goal, int k) {
final 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 <= n; ++j) {
// Add a new song
dp[i][j] = (dp[i][j] + dp[i-1][j-1] * (n - (j-1))) % MOD;
// Add a previously used song (not in last k)
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;
let dp = Array.from({length: goal + 1}, () => Array(n + 1).fill(0));
dp[0][0] = 1;
for (let i = 1; i <= goal; ++i) {
for (let j = 1; j <= n; ++j) {
// Add a new song
dp[i][j] = (dp[i][j] + dp[i-1][j-1] * (n - (j-1))) % MOD;
// Add a previously used song (not in last k)
if (j > k) {
dp[i][j] = (dp[i][j] + dp[i-1][j] * (j - k)) % MOD;
}
}
}
return dp[goal][n];
};