题目
标题和出处
标题:除自身以外数组的乘积
难度
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} 2≤nums.length≤105
- -30 ≤ nums[i] ≤ 30 \texttt{-30} \le \texttt{nums[i]} \le \texttt{30} -30≤nums[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 0≤i<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 0≤i<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=0∏i−1nums[k]
因此可以得到:
left [ i ] left [ i − 1 ] = nums [ i − 1 ] \dfrac{\textit{left}[i]}{\textit{left}[i-1]}=\textit{nums}[i-1] left[i−1]left[i]=nums[i−1]
整理得到:
left [ i ] = left [ i − 1 ] × nums [ i − 1 ] \textit{left}[i]=\textit{left}[i-1] \times \textit{nums}[i-1] left[i]=left[i−1]×nums[i−1]
对于 right \textit{right} right,显然 right [ n − 1 ] = 1 \textit{right}[n-1]=1 right[n−1]=1。当 0 ≤ i < n − 1 0 \le i<n-1 0≤i<n−1 时, 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+1∏n−1nums[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 0≤i<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 0≤i<n−1, 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 n−1 到 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)。除了返回值以外,使用的空间复杂度是常数。