按规则计算统计结果

标签: 数组 前缀和

难度: Medium

为了深入了解这些生物群体的生态特征,你们进行了大量的实地观察和数据采集。数组 arrayA 记录了各个生物群体数量数据,其中 arrayA[i] 表示第 i 个生物群体的数量。请返回一个数组 arrayB,该数组为基于数组 arrayA 中的数据计算得出的结果,其中 arrayB[i] 表示将第 i 个生物群体的数量从总体中排除后的其他数量的乘积。

示例 1:

输入:arrayA = [2, 4, 6, 8, 10]
输出:[1920, 960, 640, 480, 384]

提示:

  • 所有元素乘积之和不会溢出 32 位整数
  • arrayA.length <= 100000

Submission

运行时间: 68 ms

内存: 22.5 MB

class Solution:
    def constructArr(self, a: List[int]) -> List[int]:
        n = len(a)
        b = [1] * n
        right = 1
        for i in range(1, n):
            b[i] = b[i-1] * a[i-1]
        for i in range(n-2, -1, -1):
            right *= a[i+1]
            b[i] *= right
        return b

Explain

该题解通过两次遍历数组来构建结果数组 `arrayB`。第一次遍历从左到右,用来计算每个位置左边所有元素的乘积;第二次遍历从右到左,用来将之前计算的结果乘以该位置右边所有元素的乘积。这样,每个位置上的值最终就是除了自己以外所有其他元素的乘积。具体做法为:第一次遍历时,用一个累乘的方式计算当前位置左边所有元素的乘积,并存储到 `arrayB` 中。第二次遍历时,用一个变量 `right` 来累乘当前位置右边的元素,并更新 `arrayB[i]`。

时间复杂度: O(n)

空间复杂度: O(n)


class Solution:
    def constructArr(self, a: List[int]) -> List[int]:
        n = len(a)
        b = [1] * n  # 初始化结果数组
        right = 1  # 初始化右侧乘积变量
        for i in range(1, n):
            b[i] = b[i-1] * a[i-1]  # 计算当前位置左侧所有元素的乘积
        for i in range(n-2, -1, -1):
            right *= a[i+1]  # 更新右侧乘积
            b[i] *= right  # 乘以右侧乘积更新结果数组
        return b



Explore

在题解的算法中,初始化`b[0]`为1是因为每个位置上的值应该是其左右两侧所有元素的乘积。对于第一个元素`b[0]`,其左侧没有任何元素,因此左侧的乘积应该是乘法的单位元素,即1。这保证了`b[0]`在第一次遍历结束时正确地代表了左侧的乘积,而不会影响最终结果。

本算法处理数组中存在0的情况时,不需要特殊处理。由于算法是分别计算左侧和右侧的乘积,如果`arrayA`中的某个位置i是0,则在计算`b[i]`时,其左侧或右侧乘积会包含这个0,自然使得`b[i]=0`。对于其他位置j,只要它们的乘积中不包含这个0(即0不在它们的左侧或右侧),它们的值不会受到影响。如果`arrayA`中有多个0,则除了那些0的位置外,其他所有位置的乘积也将为0。

使用变量`right`来保存右侧的乘积是一种空间效率高的方法,因为它避免了额外数组的需求。这种方法涉及两次遍历,每次遍历的时间复杂度均为O(n),因此总的时间复杂度仍为O(n)。在处理非常大的数组时,这种方法的时间效率是线性的,不存在额外的性能瓶颈。

在反向遍历开始之前,`right`被初始化为1,这与左侧乘积类似,代表了乘法的单位元素,因为数组的最右端元素右侧没有其他元素。当`i = n-2`时(即数组的倒数第二个元素),`right`的值更新为`a[n-1]`(即最后一个元素的值),这是因为当遍历到倒数第二个元素时,其右侧只有一个元素。此后,每向左移动一个位置,`right`都会乘以当前位置右侧的第一个元素,这样每个位置的`right`都正确地表示了该位置右侧所有元素的乘积。