zoukankan      html  css  js  c++  java
  • [LeetCode 920] Number of Music Playlists

    Your music player contains N different songs and she wants to listen to L (not necessarily different) songs during your trip.  You create a playlist so that:

    • Every song is played at least once
    • A song can only be played again only if K other songs have been played

    Return the number of possible playlists.  As the answer can be very large, return it modulo 10^9 + 7.

    Example 1:

    Input: N = 3, L = 3, K = 1
    Output: 6
    Explanation: There are 6 possible playlists. [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1].
    

    Example 2:

    Input: N = 2, L = 3, K = 0
    Output: 6
    Explanation: There are 6 possible playlists. [1, 1, 2], [1, 2, 1], [2, 1, 1], [2, 2, 1], [2, 1, 2], [1, 2, 2]
    

    Example 3:

    Input: N = 2, L = 3, K = 1
    Output: 2
    Explanation: There are 2 possible playlists. [1, 2, 1], [2, 1, 2]
    

    Note:

    1. 0 <= K < N <= L <= 100

    A brute force solution is to check all possible permutations and add those that meet the given requirements to the final count. The runtime is O(L!) * O(L), which is infeasible for a input size up to 100. The following table can be used to guess the required time complexity of the algorithm that solves a problem, assuming a time limit of one second.

    Input size  required time complexity
    n <= 10 O(n !)
    n <= 20 O(2 ^ n)
    n <= 500 O(n ^ 3)
    n <= 5000 O(n ^ 2)
    n <= 10^6 O(nlogn) or O(n)
    n is large O(1) or O(logn)
      

    Anyway, back to business.  If we consider only one song's choice at a time, we can solve this problem recursively. For the last picked song, there can only be two cases. Either it is not picked before or it has been picked before. 

    The recursive formula is as following. If the last song is never picked before, the original problem F(N, L, K) is reduced to F(N - 1, L - 1, K) and we have N songs to choose from. Or the last song has been picked before, the original problem is reduced to F(N, L - 1, K) and we have N - K songs to choose from.(This satisfies the K buffer requirement)

    F(N, L, K) = F(N - 1, L - 1) * N + F(N, L - 1, K) * (N - K)

    There are two base cases:

    1. N == K + 1, this means the number of available songs just meet the buffer requirement. There are N ! permutations and for every permutation, the rest of song choices is fixed since any other ordering breaks the buffering rule.

    2. N == L, since each song must be picked at least once,  there are N ! possible orderings.

    The recursion tree reveals that there will be a lot of redundant recursive computations. For instance, F(N - 1, L - 2) will be computed twice. This is an indication of dynamic programming. A good trick of computing factorials is to use a 1D arrray to store all factorials. This avoids redundant computations as well.

    Both dp solutions have a runtime of O((N - K) * (L - K)) and space of O(N * L).

    Top down recursion with memoization 

    class Solution {  
        private int mod = (int)1e9 + 7;
        private long[] factorial;
        private long[][] dp;
        public int numMusicPlaylists(int N, int L, int K) {     
            factorial = new long[N + 1];
            dp = new long[N + 1][L + 1];
            factorial[0] = 1;
            for(int i = 1; i <= N; i++) {
                factorial[i] = factorial[i - 1] * i % mod;
            }
                  
            return (int)recursionWithMemo(N, L, K);
        }
        private long recursionWithMemo(int n, int l, int k) {
            if(dp[n][l] == 0) {
                if(n == l || n == k + 1) {
                    dp[n][l] = factorial[n];
                }
                else {
                    dp[n][l] = (recursionWithMemo(n - 1, l - 1, k) * n + recursionWithMemo(n, l - 1, k) * (n - k)) % mod;
                }
            }
            return dp[n][l];
        }
    }

    Bottom up DP

    class Solution {
        public int numMusicPlaylists(int N, int L, int K) {
            int mod = (int)1e9 + 7;
            long[] factorial = new long[N + 1];
            factorial[0] = 1;
            for(int i = 1; i <= N; i++) {
                factorial[i] = factorial[i - 1] * i % mod;
            }
            long[][] dp = new long[N + 1][L + 1];
            for(int i = K + 1; i <= N; i++) {
                for(int j = i; j <= L; j++) {
                    if(i == j || i == K + 1) {
                        dp[i][j] = factorial[i];
                    }
                    else {
                        dp[i][j] = (dp[i - 1][j - 1] * i + dp[i][j - 1] * (i - K)) % mod;
                    }
                }
            }
            return (int)dp[N][L];
        }
    }
  • 相关阅读:
    ffmpeg命令行获取RTSP流并每秒截取一张解码存储为jpg
    快速排序
    ffmpeg参数中文详细解释
    Docker
    elasticsearch的安装和配置
    C 语言-运算符(算术运算符,类型转换,赋值运算符,自增,自减,sizeof 运算符,逗号运算符,关系运算符,逻辑运算符,三目运算符)
    c语言-printf函数和scanf函数简单使用
    将JPA通用的实现方法封装成一个抽象类进行拓展
    工具类里的静态变量如何获取application.yml或者application.properties里的属性
    记java实体类属性名为全部为大写踩的坑(基础)
  • 原文地址:https://www.cnblogs.com/lz87/p/11651570.html
Copyright © 2011-2022 走看看