Sunday, August 6th
How to Make a Mixtape
Given a number of songs, and a target number of tracks, create a playlist with every song played at least once. A song can also be replayed only after a given number k songs have been played
Image courtesy of Antony Mayfield via Wikipedia, shared under the Creative Commons Attribution 2.0 Generic license. No changes made.
So, instead we turn to dynamic programming. We consider 2 state variables, a variable i to represent the number of tracks, and j to represent the number of distinct tracks. The value dp[i][j] represents the number of possible playlists of size i with j unique tracks
The goal is to find dp[goal][n], where n is the total number of unique tracks and goal is the target number of total tracks. Both these parameters are defined by the problem. Let us approach the problem recursively
Global variables
vector<vector<long> > dp;
const int MOD = 1e9 + 7;
int kk;
int nn;
and main function calling the recursive function. The dynamic programming table dp is initialized with dimensions of goal+1 and n+1
int numMusicPlaylists(int n, int goal, int k) {
// initializing global variables
dp = vector<vector<long> >(goal + 1, vector<long>(n + 1, -1))
kk = k;
nn = n;
//return the recursive function
return recurse(goal, n);
}
To approach the problem recursively, we define a function that will return the number of playlists for a given goal number of tracks with n unique songs. Because of the large problem space, the function will return a long.
long recurse(int i, int j) {
....
}
The recursive function begins with base cases. If both i and j are 0, then there is only 1 way to meet this spec - an empty table. Otherwise, if either i and j are 0 while the other is not, then this is contradictory and cannot happen, so return 0.
// Base cases
if (i == 0 && j == 0) return 1;
if (i == 0 || j == 0) return 0;
If an entry in the dp table already exists for a given combination, then return it
if (dp[i][j] != -1) return dp[i][j];
- It's a new song
- It's a repeated song
If it's a new song, then the length of the playlist increases by 1, as does the number of unique songs. So the new number of possibilities will transition from dp[i-1][j-1]
Since the number of songs in the derivative playlist is j-1, and there are n total songs, then the number of unplayed available songs is n-(j-1), or n-j+1. So for calculating all possible combinations, we multiply dp[i-1][j-1] by n-j+1 (then modulo the answer)
dp[i][j] = (recurse(i - 1, j - 1) * (nn - j + 1)) % MOD;
There is one other case to consider: when the length of the playlist increases, but the number of unique songs stays the same because we are reusing a previously played track. This means we are transitioning from dp[i-1][j], since j doesn't increase
We need to calculate how many tracks are available to choose from. There are j previously played tracks, however we must consider the problem constraint that a song can be replayed only after k other songs have been played.
If the number of played tracks is j = k-5, then since j<k we can't play any of the previous tracks. This leads us to, in order to play a previously played track, j must be greater than k. In such an instance, then we can play up to j - k tracks
if (j > kk) {
dp[i][j] += (recurse(i - 1, j) * (j - kk)) % MOD;
dp[i][j] %= MOD;
}
The two situations are added together and modded appropriately
To complete the recursive function we return the newly calculated value
return dp[i][j];
A tricky problem, but not too bad once you figure out the state variables
A nod to the Editorial for a clear and concise explanation
Comments
Post a Comment