920. Number of Music Playlists
Problem Description
You have a music player with n
different songs. You want to create a playlist to listen to exactly goal
songs during your trip (songs can repeat in the playlist). To keep things interesting and avoid boredom, your playlist must follow these rules:
-
Every song must be played at least once - All
n
songs must appear in your playlist at least one time. -
Replay restriction - If you want to play a song again, you must wait until at least
k
other different songs have been played since the last time you played it.
For example, if k = 2
and you play song A, you need to play at least 2 other different songs before you can play song A again.
Given three integers:
n
: the number of different songs availablegoal
: the total length of the playlist (total number of songs to play)k
: the minimum number of different songs that must be played before repeating a song
Return the number of different valid playlists you can create. Since this number can be very large, return the answer modulo 10^9 + 7
.
The problem uses dynamic programming where f[i][j]
represents the number of ways to create a playlist of length i
using exactly j
different songs. The transition considers two cases:
- Adding a new song that hasn't been played yet: there are
(n - j + 1)
choices - Repeating a song that was already played: there are
(j - k)
choices (only ifj > k
)
The final answer is f[goal][n]
, representing playlists of length goal
that use all n
songs.
Intuition
The key insight is to think about building the playlist step by step, keeping track of how many songs we've used so far. When constructing a playlist, at each position we need to make a choice: either play a new song we haven't played before, or replay a song we've already used.
Let's think about what state we need to track. At any point in building our playlist, we care about:
- How many songs we've added to the playlist so far (let's call this
i
) - How many different songs we've used (let's call this
j
)
This naturally leads to a dynamic programming approach where f[i][j]
represents the number of ways to create a playlist of length i
using exactly j
different songs.
Now, when we're at position i
in our playlist and have used j
different songs, we have two choices:
Choice 1: Play a new song
- We can only do this if
j < n
(we haven't used all songs yet) - We had
j - 1
different songs before, and now we're adding thej
-th one - How many new songs can we choose from? We have
n
total songs, and we've already usedj - 1
, so we haven - (j - 1) = n - j + 1
options - This contributes
f[i - 1][j - 1] * (n - j + 1)
to our count
Choice 2: Replay an existing song
- We can only do this if we've already used some songs (
j > 0
) - Due to the constraint, we can only replay songs that were played more than
k
songs ago - If we have
j
different songs in our rotation and need to skip the lastk
songs, we havej - k
songs available to replay - This only works if
j > k
(otherwise we don't have enough songs to satisfy the gap requirement) - This contributes
f[i - 1][j] * (j - k)
to our count
The base case is f[0][0] = 1
- there's exactly one way to create an empty playlist with zero songs.
Since we need all n
songs to appear at least once in our goal
-length playlist, our answer is f[goal][n]
.
Learn more about Math, Dynamic Programming and Combinatorics patterns.
Solution Approach
We implement the dynamic programming solution using a 2D array f
where f[i][j]
represents the number of ways to create a playlist of length i
using exactly j
different songs.
Initialization:
- Create a 2D array
f
of size(goal + 1) × (n + 1)
initialized with zeros - Set the base case
f[0][0] = 1
(one way to create an empty playlist)
State Transition:
For each position i
from 1
to goal
and for each number of different songs j
from 1
to n
, we calculate f[i][j]
based on two scenarios:
-
Adding a new song:
- Previous state:
f[i - 1][j - 1]
(playlist of lengthi - 1
withj - 1
different songs) - Number of choices:
(n - j + 1)
new songs available - Contribution:
f[i][j] += f[i - 1][j - 1] * (n - j + 1)
- Previous state:
-
Replaying an existing song:
- Previous state:
f[i - 1][j]
(playlist of lengthi - 1
withj
different songs) - Condition: Only possible if
j > k
(we have enough songs to satisfy the gap requirement) - Number of choices:
(j - k)
songs that can be replayed - Contribution:
f[i][j] += f[i - 1][j] * (j - k)
- Previous state:
Implementation Details:
for i in range(1, goal + 1):
for j in range(1, n + 1):
# Add a new song
f[i][j] = f[i - 1][j - 1] * (n - j + 1)
# Replay an existing song (if possible)
if j > k:
f[i][j] += f[i - 1][j] * (j - k)
# Apply modulo to prevent overflow
f[i][j] %= mod
Time Complexity: O(goal × n)
as we iterate through all states in our DP table.
Space Complexity: O(goal × n)
for storing the DP table.
The final answer is f[goal][n]
, which represents the number of playlists of length goal
that include all n
songs at least once.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with n = 3
songs, goal = 3
songs in playlist, and k = 1
(must play 1 different song before repeating).
We'll build our DP table f[i][j]
where i
is playlist length and j
is number of different songs used.
Initial State:
f[0][0] = 1 (one way to make empty playlist)
All other entries start at 0
Building the DP Table:
For i = 1
(playlist of length 1):
f[1][1]
: Use 1 different song- Add new song:
f[0][0] × (3 - 1 + 1) = 1 × 3 = 3
- Can't replay (j ≤ k)
- Result:
f[1][1] = 3
- Add new song:
For i = 2
(playlist of length 2):
-
f[2][1]
: Use 1 different song (must repeat the same song)- Add new song:
f[1][0] × 3 = 0
(f[1][0] doesn't exist/is 0) - Can't replay (j = 1 ≤ k = 1)
- Result:
f[2][1] = 0
- Add new song:
-
f[2][2]
: Use 2 different songs- Add new song:
f[1][1] × (3 - 2 + 1) = 3 × 2 = 6
- Replay existing:
f[1][2] × (2 - 1) = 0
(f[1][2] is 0) - Result:
f[2][2] = 6
- Add new song:
For i = 3
(playlist of length 3):
-
f[3][1]
: Use 1 different song- Add new song:
f[2][0] × 3 = 0
- Can't replay (j ≤ k)
- Result:
f[3][1] = 0
- Add new song:
-
f[3][2]
: Use 2 different songs- Add new song:
f[2][1] × 2 = 0 × 2 = 0
- Replay existing:
f[2][2] × (2 - 1) = 6 × 1 = 6
- Result:
f[3][2] = 6
- Add new song:
-
f[3][3]
: Use 3 different songs (all songs used)- Add new song:
f[2][2] × (3 - 3 + 1) = 6 × 1 = 6
- Replay existing:
f[2][3] × (3 - 1) = 0
(f[2][3] is 0) - Result:
f[3][3] = 6
- Add new song:
Final DP Table:
j=0 j=1 j=2 j=3 i=0 1 0 0 0 i=1 0 3 0 0 i=2 0 0 6 0 i=3 0 0 6 6
Answer: f[3][3] = 6
The 6 valid playlists are (using songs A, B, C):
- [A, B, C] - each song played once
- [A, C, B] - each song played once
- [B, A, C] - each song played once
- [B, C, A] - each song played once
- [C, A, B] - each song played once
- [C, B, A] - each song played once
Note that we can't have playlists like [A, A, B] because that would only use 2 different songs, not all 3 as required.
Solution Implementation
1class Solution:
2 def numMusicPlaylists(self, n: int, goal: int, k: int) -> int:
3 """
4 Calculate the number of possible playlists of length 'goal' containing all 'n' songs,
5 where each song must be separated by at least 'k' other songs before being repeated.
6
7 Args:
8 n: Total number of unique songs available
9 goal: Target length of the playlist
10 k: Minimum number of other songs between repetitions of the same song
11
12 Returns:
13 Number of valid playlists modulo 10^9 + 7
14 """
15 MOD = 10**9 + 7
16
17 # dp[i][j] represents the number of playlists of length i containing exactly j unique songs
18 dp = [[0] * (n + 1) for _ in range(goal + 1)]
19
20 # Base case: empty playlist with 0 songs
21 dp[0][0] = 1
22
23 # Fill the DP table
24 for playlist_length in range(1, goal + 1):
25 for unique_songs in range(1, n + 1):
26 # Case 1: Add a new song that hasn't been played yet
27 # We have (n - unique_songs + 1) choices for the new song
28 dp[playlist_length][unique_songs] = dp[playlist_length - 1][unique_songs - 1] * (n - unique_songs + 1)
29
30 # Case 2: Replay an existing song (if allowed by the k constraint)
31 # We can only replay if we have more than k unique songs already
32 # and we can choose from (unique_songs - k) songs that are eligible for replay
33 if unique_songs > k:
34 dp[playlist_length][unique_songs] += dp[playlist_length - 1][unique_songs] * (unique_songs - k)
35
36 # Apply modulo to prevent overflow
37 dp[playlist_length][unique_songs] %= MOD
38
39 # Return the number of playlists of length 'goal' containing all 'n' unique songs
40 return dp[goal][n]
41
1class Solution {
2 public int numMusicPlaylists(int n, int goal, int k) {
3 // Modulo value for preventing integer overflow
4 final int MOD = (int) 1e9 + 7;
5
6 // dp[i][j] represents the number of playlists of length i that contain exactly j unique songs
7 // i: current playlist length (0 to goal)
8 // j: number of unique songs used so far (0 to n)
9 long[][] dp = new long[goal + 1][n + 1];
10
11 // Base case: empty playlist with no songs
12 dp[0][0] = 1;
13
14 // Fill the DP table
15 for (int playlistLength = 1; playlistLength <= goal; playlistLength++) {
16 for (int uniqueSongs = 1; uniqueSongs <= n; uniqueSongs++) {
17 // Case 1: Add a new song that hasn't been played before
18 // We can choose from (n - uniqueSongs + 1) songs that haven't been used yet
19 // This transitions from dp[playlistLength - 1][uniqueSongs - 1]
20 dp[playlistLength][uniqueSongs] = dp[playlistLength - 1][uniqueSongs - 1] * (n - uniqueSongs + 1);
21
22 // Case 2: Replay an existing song (if allowed by constraint k)
23 // We can only replay a song if we have more than k unique songs already
24 // We can choose from (uniqueSongs - k) songs that are available for replay
25 if (uniqueSongs > k) {
26 dp[playlistLength][uniqueSongs] += dp[playlistLength - 1][uniqueSongs] * (uniqueSongs - k);
27 }
28
29 // Apply modulo to prevent overflow
30 dp[playlistLength][uniqueSongs] %= MOD;
31 }
32 }
33
34 // Return the number of playlists of length 'goal' using all 'n' songs
35 return (int) dp[goal][n];
36 }
37}
38
1class Solution {
2public:
3 int numMusicPlaylists(int n, int goal, int k) {
4 const int MOD = 1e9 + 7;
5
6 // dp[i][j] represents the number of playlists of length i using exactly j unique songs
7 long long dp[goal + 1][n + 1];
8 memset(dp, 0, sizeof(dp));
9
10 // Base case: empty playlist with 0 unique songs
11 dp[0][0] = 1;
12
13 // Fill the DP table
14 for (int playlistLength = 1; playlistLength <= goal; ++playlistLength) {
15 for (int uniqueSongs = 1; uniqueSongs <= n; ++uniqueSongs) {
16 // Case 1: Add a new song that hasn't been played yet
17 // We have (n - uniqueSongs + 1) choices for the new song
18 dp[playlistLength][uniqueSongs] = dp[playlistLength - 1][uniqueSongs - 1] * (n - uniqueSongs + 1);
19
20 // Case 2: Replay a song that has already been played
21 // We can only replay if we have more than k unique songs already
22 // and we can choose from (uniqueSongs - k) songs that are eligible for replay
23 if (uniqueSongs > k) {
24 dp[playlistLength][uniqueSongs] += dp[playlistLength - 1][uniqueSongs] * (uniqueSongs - k);
25 }
26
27 // Apply modulo to prevent overflow
28 dp[playlistLength][uniqueSongs] %= MOD;
29 }
30 }
31
32 // Return the number of playlists with exactly 'goal' length using all 'n' unique songs
33 return dp[goal][n];
34 }
35};
36
1/**
2 * Calculates the number of possible playlists of length 'goal' containing exactly 'n' different songs,
3 * where each song must be played at least once and a song can only be repeated if at least 'k' other songs have been played.
4 *
5 * @param n - Total number of different songs available
6 * @param goal - Target length of the playlist
7 * @param k - Minimum number of other songs that must be played before repeating a song
8 * @returns The number of possible playlists modulo 10^9 + 7
9 */
10function numMusicPlaylists(n: number, goal: number, k: number): number {
11 const MOD: number = 1e9 + 7;
12
13 // dp[i][j] represents the number of playlists of length i containing exactly j different songs
14 const dp: number[][] = new Array(goal + 1)
15 .fill(0)
16 .map(() => new Array(n + 1).fill(0));
17
18 // Base case: empty playlist with 0 songs
19 dp[0][0] = 1;
20
21 // Fill the DP table
22 for (let playlistLength = 1; playlistLength <= goal; playlistLength++) {
23 for (let uniqueSongs = 1; uniqueSongs <= n; uniqueSongs++) {
24 // Case 1: Add a new song that hasn't been played yet
25 // We have (n - uniqueSongs + 1) choices for the new song
26 dp[playlistLength][uniqueSongs] = dp[playlistLength - 1][uniqueSongs - 1] * (n - uniqueSongs + 1);
27
28 // Case 2: Repeat a song that has already been played
29 // We can only repeat if we have more than k unique songs already
30 // and we can choose from (uniqueSongs - k) songs to repeat
31 if (uniqueSongs > k) {
32 dp[playlistLength][uniqueSongs] += dp[playlistLength - 1][uniqueSongs] * (uniqueSongs - k);
33 }
34
35 // Apply modulo to prevent overflow
36 dp[playlistLength][uniqueSongs] %= MOD;
37 }
38 }
39
40 // Return the number of playlists with exactly 'goal' length and 'n' different songs
41 return dp[goal][n];
42}
43
Time and Space Complexity
The time complexity is O(goal × n)
. The code uses two nested loops - the outer loop iterates from 1 to goal
(which runs goal
times), and the inner loop iterates from 1 to n
(which runs n
times). Inside the nested loops, all operations including arithmetic calculations, array access, and modulo operations take constant time O(1)
. Therefore, the overall time complexity is O(goal × n)
.
The space complexity is O(goal × n)
. The code creates a 2D array f
with dimensions (goal + 1) × (n + 1)
, which requires O((goal + 1) × (n + 1))
space. This simplifies to O(goal × n)
since we drop the constant additions when expressing big-O notation. No other data structures are created that would affect the space complexity, so the total space complexity remains O(goal × n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Counting Available Songs
The Pitfall:
When adding a new song that hasn't been played yet, developers often make mistakes in calculating how many songs are available to choose from. A common error is using (n - j)
instead of (n - j + 1)
.
Why It Happens:
- If we have
n
total songs and have already usedj - 1
different songs, we're now choosing thej
-th unique song - The number of remaining songs is
n - (j - 1) = n - j + 1
- It's easy to incorrectly think "we have used
j
songs, son - j
remain"
Incorrect Code:
# Wrong: This assumes we've already used j songs dp[i][j] = dp[i - 1][j - 1] * (n - j) # ❌
Correct Code:
# Right: We're choosing the j-th song from (n - j + 1) available options dp[i][j] = dp[i - 1][j - 1] * (n - j + 1) # ✅
2. Integer Overflow from Not Applying Modulo Early
The Pitfall: Forgetting to apply the modulo operation after each calculation step, or only applying it at the very end, can cause integer overflow even in languages like Python that handle big integers.
Why It Happens:
- The intermediate values grow exponentially large
- Even though Python handles arbitrary precision integers, the computation becomes extremely slow
- In other languages like Java or C++, this causes actual overflow
Incorrect Code:
# Wrong: Accumulating large values without modulo dp[i][j] = dp[i - 1][j - 1] * (n - j + 1) if j > k: dp[i][j] += dp[i - 1][j] * (j - k) # Only applying modulo at the end - values can get huge! ❌ dp[i][j] = dp[i][j] % MOD
Correct Code:
# Right: Apply modulo after each operation 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 # ✅
3. Incorrect Base Case Initialization
The Pitfall:
Setting dp[0][0] = 0
or forgetting to initialize the base case entirely, which propagates zeros throughout the entire DP table.
Why It Happens:
- It seems counterintuitive that there's "one way" to create an empty playlist
- Developers might think an empty playlist should have zero ways
Incorrect Code:
# Wrong: No valid playlists will be counted dp[0][0] = 0 # ❌ # Or forgetting initialization entirely
Correct Code:
# Right: There's exactly one way to have an empty playlist dp[0][0] = 1 # ✅
4. Misunderstanding the Replay Constraint
The Pitfall:
Using j >= k
instead of j > k
when checking if we can replay a song, or miscalculating the number of replayable songs.
Why It Happens:
- The constraint states we need
k
OTHER songs between replays - If we have exactly
k
unique songs, we can't replay any of them yet because we need allk
as the "buffer"
Incorrect Code:
# Wrong: Allows replay when we only have k songs if j >= k: # ❌ dp[i][j] += dp[i - 1][j] * (j - k) # When j = k, this gives 0 choices anyway, but the logic is wrong
Correct Code:
# Right: We need MORE than k songs to have replayable options if j > k: # ✅ dp[i][j] += dp[i - 1][j] * (j - k) # When j = k+1, we have 1 song that can be replayed
How many times is a tree node visited in a depth first search?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Want a Structured Path to Master System Design Too? Don’t Miss This!