数组题目:除自身以外数组的乘积

题目

标题和出处

标题:除自身以外数组的乘积

出处:238. 除自身以外数组的乘积

难度

5 级

题目描述

要求

给你一个整数数组 nums \texttt{nums} nums,返回数组 answer \texttt{answer} answer,其中 answer[i] \texttt{answer[i]} answer[i] 等于 nums \texttt{nums} nums 中除 nums[i] \texttt{nums[i]} nums[i] 之外其余各元素的乘积。

题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 \texttt{32} 32 位整数范围内。

要求时间复杂度是 O(n) \texttt{O(n)} O(n),不使用除法。

示例

示例 1:

输入: [1,2,3,4] \texttt{[1,2,3,4]} [1,2,3,4]
输出: [24,12,8,6] \texttt{[24,12,8,6]} [24,12,8,6]

示例 2:

输入: [-1,1,0,-3,3] \texttt{[-1,1,0,-3,3]} [-1,1,0,-3,3]
输出: [0,0,9,0,0] \texttt{[0,0,9,0,0]} [0,0,9,0,0]

数据范围

  • 2 ≤ nums.length ≤ 10 5 \texttt{2} \le \texttt{nums.length} \le \texttt{10}^\texttt{5} 2nums.length105
  • -30 ≤ nums[i] ≤ 30 \texttt{-30} \le \texttt{nums[i]} \le \texttt{30} -30nums[i]30
  • 题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 \texttt{32} 32 位整数范围内

进阶

你可以使用 O(1) \texttt{O(1)} O(1) 空间复杂度完成这个题目吗?出于对空间复杂度分析的目的,输出数组不被视为额外空间。

解法一

思路和算法

对于长度为 n n n 的数组 nums \textit{nums} nums,在不允许使用除法的情况下,对于每个 0 ≤ i < n 0 \le i<n 0i<n answer [ i ] \textit{answer}[i] answer[i] 的值为 nums \textit{nums} nums 中除 nums [ i ] \textit{nums}[i] nums[i] 之外的其他元素的乘积。如果直接计算乘积,则 answer \textit{answer} answer 的每个元素需要 O ( n ) O(n) O(n) 的时间计算,总时间复杂度为 O ( n 2 ) O(n^2) O(n2)

为了将总时间复杂度降到 O ( n ) O(n) O(n),必须将 answer \textit{answer} answer 的每个元素的计算时间降到 O ( 1 ) O(1) O(1)。注意到对任意 0 ≤ i < n 0 \le i<n 0i<n answer [ i ] \textit{answer}[i] answer[i] 的值等于数组 nums \textit{nums} nums 的下标 i i i 左侧的全部元素与下标 i i i 右侧的全部元素的乘积,因此只要计算出数组 nums \textit{nums} nums 的每个元素的左侧元素乘积与右侧元素乘积,即可得到 answer \textit{answer} answer 的值。

创建两个长度为 n n n 的数组 left \textit{left} left right \textit{right} right,分别用于存储左侧元素乘积与右侧元素乘积,需要在 O ( n ) O(n) O(n) 的时间内完成 left \textit{left} left right \textit{right} right 的元素计算。

对于 left \textit{left} left,显然 left [ 0 ] = 1 \textit{left}[0]=1 left[0]=1。当 0 < i < n 0<i<n 0<i<n 时, left [ i ] \textit{left}[i] left[i] 的计算如下:

left [ i ] = ∏ k = 0 i − 1 nums [ k ] \textit{left}[i]=\prod\limits_{k=0}^{i-1} \textit{nums}[k] left[i]=k=0i1nums[k]

因此可以得到:

left [ i ] left [ i − 1 ] = nums [ i − 1 ] \dfrac{\textit{left}[i]}{\textit{left}[i-1]}=\textit{nums}[i-1] left[i1]left[i]=nums[i1]

整理得到:

left [ i ] = left [ i − 1 ] × nums [ i − 1 ] \textit{left}[i]=\textit{left}[i-1] \times \textit{nums}[i-1] left[i]=left[i1]×nums[i1]

对于 right \textit{right} right,显然 right [ n − 1 ] = 1 \textit{right}[n-1]=1 right[n1]=1。当 0 ≤ i < n − 1 0 \le i<n-1 0i<n1 时, right [ i ] \textit{right}[i] right[i] 的计算如下:

right [ i ] = ∏ k = i + 1 n − 1 nums [ k ] \textit{right}[i]=\prod\limits_{k=i+1}^{n-1} \textit{nums}[k] right[i]=k=i+1n1nums[k]

因此可以得到:

right [ i ] right [ i + 1 ] = nums [ i + 1 ] \dfrac{\textit{right}[i]}{\textit{right}[i+1]}=\textit{nums}[i+1] right[i+1]right[i]=nums[i+1]

整理得到:

right [ i ] = right [ i + 1 ] × nums [ i + 1 ] \textit{right}[i]=\textit{right}[i+1] \times \textit{nums}[i+1] right[i]=right[i+1]×nums[i+1]

根据上述递推关系,可以在 O ( n ) O(n) O(n) 的时间内完成 left \textit{left} left right \textit{right} right 的元素计算。

计算 left \textit{left} left right \textit{right} right 的元素之后,对于 0 ≤ i < n 0 \le i<n 0i<n,令 answer [ i ] = left [ i ] × right [ i ] \textit{answer}[i]=\textit{left}[i] \times \textit{right}[i] answer[i]=left[i]×right[i],则 answer \textit{answer} answer 即为输出数组。

代码

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] left = new int[n];
        left[0] = 1;
        for (int i = 1; i < n; i++) {
            left[i] = left[i - 1] * nums[i - 1];
        }
        int[] right = new int[n];
        right[n - 1] = 1;
        for (int i = n - 2; i >= 0; i--) {
            right[i] = right[i + 1] * nums[i + 1];
        }
        int[] answer = new int[n];
        for (int i = 0; i < n; i++) {
            answer[i] = left[i] * right[i];
        }
        return answer;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。计算数组 left \textit{left} left right \textit{right} right 的元素各需要 O ( n ) O(n) O(n) 的时间,计算 answer \textit{answer} answer 也需要 O ( n ) O(n) O(n) 的时间,因此总时间复杂度是 O ( n ) O(n) O(n)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要创建两个长度为 n n n 的数组 left \textit{left} left right \textit{right} right

解法二

思路和算法

解法一除了输出数组 answer \textit{answer} answer 以外,还创建了两个额外的数组 left \textit{left} left right \textit{right} right。如果要将空间复杂度降到 O ( 1 ) O(1) O(1),就必须在输出数组 answer \textit{answer} answer 上完成 left \textit{left} left right \textit{right} right 的计算,而不能创建辅助数组。

对于解法一中的 left \textit{left} left 的计算,可以直接在 answer \textit{answer} answer 上进行,即从左到右遍历数组 nums \textit{nums} nums 并更新 answer \textit{answer} answer 的值,遍历结束后, answer \textit{answer} answer 中的每个元素即为左侧元素乘积。

对于解法一中的 right \textit{right} right 的计算,并不需要新开数组存储元素值。注意到解法一中,对于 0 ≤ i < n − 1 0 \le i<n-1 0i<n1 right [ i ] \textit{right}[i] right[i] 的值只和 right [ i + 1 ] \textit{right}[i+1] right[i+1] nums [ i + 1 ] \textit{nums}[i+1] nums[i+1] 有关,而和 right [ i + 2 ] \textit{right}[i+2] right[i+2] 以及更右边的元素无关,因此可以从右到左遍历数组 nums \textit{nums} nums,使用一个变量存储右侧元素乘积。

具体做法是,使用变量 right \textit{right} right 存储右侧元素乘积,初始时 right = 1 \textit{right}=1 right=1。遍历 i i i n − 1 n-1 n1 0 0 0 的每个值,对于每个 i i i,此时的 right \textit{right} right 表示数组 nums \textit{nums} nums 的下标 i i i 右侧的全部元素的乘积,因此令 answer [ i ] = answer [ i ] × right \textit{answer}[i]=\textit{answer}[i] \times \textit{right} answer[i]=answer[i]×right,然后令 right = right × nums [ i ] \textit{right}=\textit{right} \times \textit{nums}[i] right=right×nums[i]。该过程和解法一中的数组 right \textit{right} right 的计算类似,区别在于此处的 right \textit{right} right 是一个变量而不是一个数组。

最终得到的 answer \textit{answer} answer 即为输出数组。

代码

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] answer = new int[n];
        answer[0] = 1;
        for (int i = 1; i < n; i++) {
            answer[i] = answer[i - 1] * nums[i - 1];
        }
        int right = 1;
        for (int i = n - 1; i >= 0; i--) {
            answer[i] *= right;
            right *= nums[i];
        }
        return answer;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要对 nums \textit{nums} nums 正向遍历一次和反向遍历一次,计算 answer \textit{answer} answer 的值。

  • 空间复杂度: O ( 1 ) O(1) O(1)。除了返回值以外,使用的空间复杂度是常数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值