【JD算法题】定义一个数组的权值为,该数组最大值的出现次数。求长度为n且每个元素范围都在[1,n]的所有数组的权值之和。

Problem

小红定义一个数组的权值为,该数组最大值的出现次数。
例如[2,3,3,4]的权值为1,[2,3,3,3]的权值为3.
小红想知道,长度为n,且每个元素范围都在[1,n]的数组(显然有n^n个数组),这些数组的权值之和是多少?由于该数可能过大,请对 1 0 9 + 7 10^9+7 109+7取模。

输入描述:一个正整数n。1<=n<=1000
输出描述:所有数组的权值之和。

示例:输入:2; 输出:6

思考

  1. 采用回溯法,像写排列数一样列出所有情况。会超时
  2. 考虑更普适的方法,以内存换时间:

考虑长度为n的数组,可能的权值为1,2,3,4,…, n;其中,每种权值可以搭配 “该数组的最大值” 为1,2,3,4,…, n的情况。由此可以构建一个 n × n n \times n n×n的矩阵。

举个例子,n=5的情况:

权值 max=1 max=2 max=3 max=4 max=5
1 0 C 5 1 ∗ M 4 1 C_5^1 * M_4^1 C51M41 C 5 1 ∗ M 4 2 C_5^1 * M_4^2 C51M42 C 5 1 ∗ M 4 3 C_5^1 * M_4^3 C51M43 C 5 1 ∗ M 4 4 C_5^1 * M_4^4 C51M44
2 0 C 5 2 ∗ M 3 1 C_5^2 * M_3^1 C52M31 C 5 2 ∗ M 3 2 C_5^2 * M_3^2 C52M32 C 5 2 ∗ M 3 3 C_5^2 * M_3^3 C52M33 C 5 2 ∗ M 3 4 C_5^2 * M_3^4 C52M34
3 0 C 5 3 ∗ M 2 1 C_5^3 * M_2^1 C53M21 C 5 3 ∗ M 2 2 C_5^3 * M_2^2 C53M22 C 5 3 ∗ M 2 3 C_5^3 * M_2^3 C53M23 C 5 3 ∗ M 2 4 C_5^3 * M_2^4 C53M24
4 0 C 5 4 ∗ M 1 1 C_5^4 * M_1^1 C54M11 C 5 4 ∗ M 1 2 C_5^4 * M_1^2 C54M12 C 5 4 ∗ M 1 3 C_5^4 * M_1^3 C54M13 C 5 4 ∗ M 1 4 C_5^4 * M_1^4 C54M14
5 1 C 5 5 C_5^5 C55 C 5 5 C_5^5 C55 C 5 5 C_5^5 C55 C 5 5 C_5^5 C55

其中, M i j M_i^j Mij 代表 有 i i i 个位置,要填 j j j 个数。即有 M i j = j i M_i^j = j^i Mij=ji. C i j C_i^j Cij 是组合数。

以权值为2,max=4为例,代表数组中4出现了2次,5没出现,数组剩下的3个位置由‘1,2,3’三个数来填,所以有 C 5 2 ∗ M 3 3 C_5^2 * M_3^3 C52M33种情况。

如果直接简单粗暴的按这个思路写代码:

from itertools import combinations
def C(a,b):
    A = [i for i in range(0,a)]
    res = list(combinations(A, b))
    return len(res)

def M(a,b):
    return pow(b, a)

def Problem3(n):
    N_matrix = []
    res = 0
    for i in range(1,n):   # row
        row_i = [0]
        for j in range(1, n):
            row_i.append(C(n, i) * M(n-i, j))
        N_matrix.append(row_i)
        res+=i * sum(row_i)
    N_matrix.append([1]*n)
    res += n*n
    return

虽然可以算出结果,但当n=20时就已经需要2秒了,时间复杂度呈指数式增长。

注意到矩阵的第一行~倒数第二行的M乘数部分呈现固定倍数关系,比如上述矩阵第4行为:
0 0 0, C 5 4 ∗ 1 1 C_5^4*1^1 C5411, C 5 4 ∗ 2 1 C_5^4*2^1 C5421, C 5 4 ∗ 3 1 C_5^4*3^1 C5431, C 5 4 ∗ 4 1 C_5^4*4^1 C5441

第三行为:
0 0 0, C 5 3 ∗ 1 2 C_5^3*1^2 C5312, C 5 3 ∗ 2 2 C_5^3*2^2 C5322, C 5 3 ∗ 3 2 C_5^3*3^2 C5332, C 5 3 ∗ 4 2 C_5^3*4^2 C5342

不看组合数部分,则将第四行乘上 0 , 1 , 2 , 3 , 4 0, 1, 2, 3, 4 01234可以得到第三行;同理,将第三行乘上 0 , 1 , 2 , 3 , 4 0, 1, 2, 3, 4 01234可以得到第二行。

加速后的代码为:

def Problem3_speed(n):
    multiples = [i for i in range(n)]
    a = [1]*n
    row_n_i = a
    res = n * n
    for i in range(1, n):
        row_n_i = cdot(row_n_i, multiples)
        coeff = C(n,n-i)*(n-i)
        res += coeff*sum(row_n_i)
    return res

发现算n=20,已经可以由第一版本的代码的2秒加速成0.1秒,但由于组合数计算涉及大量阶乘依然很费时间。组合数公式:
C m n = m ! n ! ( m − n ) ! C_m^n = \frac{m!}{n!(m-n)!} Cmn=n!(mn)!m!
如果提前把 [ 1 ! , 2 ! , 3 ! , . . . , n ! ] [1!, 2!, 3!, ..., n!] [1!,2!,3!,...,n!]存下来,就避免了大量的重复计算。计算 [ 1 ! , 2 ! , 3 ! , . . . , n ! ] [1!, 2!, 3!, ..., n!] [1!,2!,3!,...,n!]的代码:

# [1!, 2!, 3!, ..., n!]
def factorial(n):
    a=1
    n_fac_list = []
    #for循环遍历
    for i in range(1,n+1):
        a*=i
        n_fac_list.append(a)
    return n_fac_list

而且
C m n = C m m − n C_m^n = C_m^{m-n} Cmn=Cmmn
注意到每一列的组合数都是一致的,下面写个函数,提前将这些组合数(Cn1, Cn2,Cn3,…, Cnn)存下来:

def C_table(n):
    '''
    :param n:
    :return: from row1 to row n: Cn1, Cn2,Cn3,..., Cnn
    '''
    c_table = []
    n_fac_table = factorial(n)
    if n % 2 == 0:
        for i in range(1, n//2+1):
            curr_num = n_fac_table[-1] // (n_fac_table[i-1] * n_fac_table[n-i-1])
            c_table.append(curr_num)
        c_table+=c_table[::-1][1:]
        c_table+=[1]
    else:
        for i in range(1, n // 2 + 1):
            curr_num = n_fac_table[-1] // (n_fac_table[i - 1] * n_fac_table[n - i - 1])
            c_table.append(curr_num)
        c_table += c_table[::-1]
        c_table += [1]
    return c_table

下面再写主函数的代码:

def Problem3_speed2(n):
    mods = pow(10,9)+7
    multiples = [i for i in range(n)]
    a = [1]*n
    c = C_table(n)
    row_n_i = a
    res = n * n
    for i in range(1, n):
        row_n_i = cdot(row_n_i, multiples)
        coeff = (n-i) * c[n-i-1]
        res += coeff*sum(row_n_i)
    return res%mods

测试一下时间:

if __name__ =='__main__':
    import time
    t2 = time.time()
    print(Problem3_speed2(1000))
    t3 = time.time()
    print(t3-t2)

n=1000也只需要0.4秒 ^_^
【JD算法题】定义一个数组的权值为,该数组最大值的出现次数。求长度为n且每个元素范围都在[1,n]的所有数组的权值之和。_第1张图片

你可能感兴趣的:(leetcode,算法,python,开发语言)