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

文章讨论了一种计算特定数组权值之和的问题,其中权值是数组中最大值出现的次数。对于长度为n且元素在[1,n]范围内的所有数组,提出了通过构建矩阵并利用组合数和幂运算进行优化的算法,减少了时间复杂度,从而解决了大整数模运算下的效率问题。
摘要由CSDN通过智能技术生成

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=1max=2max=3max=4max=5
10 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
20 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
30 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
40 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
51 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秒 ^_^
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
给定长度为 n 的无序数字数组,在 JavaScript 中,我们可以利用这个数组构建一个二叉树。其中,数组的每个数字代表二叉树的叶节点的权值。 首先,我们需要创建一个二叉树的节点类,每个节点包含一个值属性和左右节点属性。接下来,我们可以通过遍历数组,逐个创建叶节点,并按照二叉树的规则,将节点插入到合适的位置。 具体实现步骤如下: 1. 创建一个节点类,包含值属性(value)、左节点属性(left)和右节点属性(right)。 2. 定义一个函数,用于构建二叉树。函数输入为一个无序的数字数组数组长度 n。 3. 根据数组的第一个数字创建根节点,并将其作为当前节点。 4. 遍历数组中的其他数字,依次创建叶节点。 5. 对于每个叶节点,从根节点开始,根据当前节点的值与叶节点的值的大小关系,将叶节点插入到左树或右树中。 6. 重复步骤 4-5,直到遍历完整个数组。 7. 返回根节点,即为构建好的二叉树的根节点。 以下是一个简化的示例代码: ``` class Node { constructor(value) { this.value = value; this.left = null; this.right = null; } } function buildBinaryTree(arr, n) { if (n === 0) { return null; } const root = new Node(arr[0]); for (let i = 1; i < n; i++) { const leaf = new Node(arr[i]); let current = root; while (true) { if (leaf.value < current.value) { if (current.left) { current = current.left; } else { current.left = leaf; break; } } else { if (current.right) { current = current.right; } else { current.right = leaf; break; } } } } return root; } // 示例用法 const arr = [5, 3, 7, 1, 4, 6, 8]; const n = arr.length; const root = buildBinaryTree(arr, n); // 打印结果,方便进行验证 console.log(root); ``` 以上是通过给定长度为 n 的无序数字数组,构建一个二叉树的方法。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值