Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

920. Number of Music Playlists - Leetcode Solution

Problem Description

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:

  • Every song is unique and must be played at least once in the playlist.
  • Once a song has been played, it can only be played again if at least k other songs have been played since it was last played.
  • The answer can be very large, so you must return it modulo 10^9 + 7.

Input:

  • n: Total number of unique songs
  • goal: The length of the playlist
  • k: The minimum number of different songs that must be played before a song can be repeated
Output:
  • The number of possible playlists of length goal that use every song at least once and obey the repeat rule.

Thought Process

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.

Solution Approach

We use dynamic programming to solve the problem efficiently. Let's break down the approach:

  1. State Definition:
    • Let dp[i][j] be the number of playlists of length i that have exactly j unique songs.
  2. Base Case:
    • dp[0][0] = 1: One way to have a playlist of length 0 with 0 songs.
  3. Transition:
    • We can add a new song that hasn't been used yet. There are n - j options for this, so dp[i-1][j-1] * (n - (j-1)).
    • Or, we can repeat a song that was used before, but not in the last k songs. There are max(j - k, 0) options, so dp[i-1][j] * max(j - k, 0).
    • So, dp[i][j] = dp[i-1][j-1] * (n - (j-1)) + dp[i-1][j] * max(j - k, 0).
  4. Final Answer:
    • The answer is dp[goal][n], i.e., playlists of length goal using all n songs.
  5. Modulo:
    • Since the answer can be large, take all computations modulo 10^9 + 7.

This DP approach allows us to count valid playlists efficiently without explicit enumeration.

Example Walkthrough

Let's step through an example: n = 3, goal = 3, k = 1.

  • We want playlists of length 3 using all 3 songs, where no song repeats until at least 1 other song is played.
  • We set up a DP table dp[4][4] (since we include 0).
  • Start with dp[0][0] = 1.
  • For 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).
  • For 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).
  • For 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).
  • So, 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.

Time and Space Complexity

  • Brute-force:
    • Would enumerate all possible playlists of length goal from n songs, which is n^goal possibilities. Exponential and intractable for large n or goal.
  • Dynamic Programming Approach:
    • We fill a dp[goal+1][n+1] table. Each entry is computed in O(1) time.
    • Time Complexity: O(goal * n)
    • Space Complexity: O(goal * n)

This is efficient and feasible for the given constraints.

Summary

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.

Code Implementation

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];
};