LC 238. 除自身以外数组的乘积

题目描述

这是 LeetCode 上的 238. 除自身以外数组的乘积 ,难度为 中等

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

题目数据 保证 数组 nums 之中任意元素的全部前缀元素和后缀的乘积都在 $32$ 位 整数范围内。

请不要使用除法,且在 $O(n)$ 时间复杂度内完成此题。

示例 1:

1
2
3
输入: nums = [1,2,3,4]

输出: [24,12,8,6]

示例 2:
1
2
3
输入: nums = [-1,1,0,-3,3]

输出: [0,0,9,0,0]

提示:

  • $2 <= nums.length <= 10^5$
  • $-30 <= nums[i] <= 30$
  • 保证 数组 nums 之中任意元素的全部前缀元素和后缀的乘积都在 $32$ 位 整数范围内

进阶:你可以在 $O(1)$ 的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)


前缀和

根据题意,对于每个 $ans[i]$ 均由两部分组成:

因此我们可以运用「前缀和」思想,使用数组 s1s2 分别计算范围 $[1, x]$ 的前缀乘 和 范围 $[x, n]$ 的后缀乘法(前缀和数组下标默认从 $1$ 开始),即 $s1[i]$ 代表范围 $[1,i]$ 的前缀乘值,$s2[i]$ 代表范围 $[i, n]$ 的后缀乘值。

预处理完 s1s2 之后,即可计算每个 $ans[i - 1] = s1[i - 1] \times s2[i + 1]$(ans 数组下标从 $0$ 开始)。

Java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] s1 = new int[n + 10], s2 = new int[n + 10];
s1[0] = s2[n + 1] = 1;
for (int i = 1; i <= n; i++) s1[i] = s1[i - 1] * nums[i - 1];
for (int i = n; i >= 1; i--) s2[i] = s2[i + 1] * nums[i - 1];
int[] ans = new int[n];
for (int i = 1; i <= n; i++) ans[i - 1] = s1[i - 1] * s2[i + 1];
return ans;
}
}

C++ 代码:
1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> s1(n + 2, 1), s2(n + 2, 1);
for (int i = 1; i <= n; i++) s1[i] = s1[i - 1] * nums[i - 1];
for (int i = n; i >= 1; i--) s2[i] = s2[i + 1] * nums[i - 1];
vector<int> ans(n);
for (int i = 1; i <= n; i++) ans[i - 1] = s1[i - 1] * s2[i + 1];
return ans;
}
};

Python 代码:
1
2
3
4
5
6
7
8
9
10
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
s1, s2 = [1] * (n + 2), [1] * (n + 2)
for i in range(1, n + 1):
s1[i] = s1[i - 1] * nums[i - 1]
for i in range(n, 0, -1):
s2[i] = s2[i + 1] * nums[i - 1]
ans = [s1[i - 1] * s2[i + 1] for i in range(1, n + 1)]
return ans

TypeScript 代码:
1
2
3
4
5
6
7
8
9
function productExceptSelf(nums: number[]): number[] {
const n = nums.length;
const s1 = new Array(n + 2).fill(1), s2 = new Array(n + 2).fill(1);
for (let i = 1; i <= n; i++) s1[i] = s1[i - 1] * nums[i - 1];
for (let i = n; i >= 1; i--) s2[i] = s2[i + 1] * nums[i - 1];
const ans = new Array(n);
for (let i = 1; i <= n; i++) ans[i - 1] = s1[i - 1] * s2[i + 1];
return ans;
};

  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(n)$

前缀和(空间优化)

题目的进阶部分要求我们使用 $O(1)$ 空间来做(不算 ans 数组)。

这样很好处理,按照我们解法一的思路,将两部分分开算即可:建立 ans 数组,先从前往后遍历 nums,计算每个 $ans[i]$ 前缀乘值部分,再从后往前遍历 nums,计算每个 $ans[i]$ 后缀乘值的部分,两部分相乘即是最终的 $ans[i]$。

Java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] ans = new int[n];
for (int i = 1, j = 1; i <= n; i++) {
ans[i - 1] = j; j *= nums[i - 1];
}
for (int i = n, j = 1; i >= 1; i--) {
ans[i - 1] *= j; j *= nums[i - 1];
}
return ans;
}
}

C++ 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n, 1);
for (int i = 1, j = 1; i <= n; i++) {
ans[i - 1] = j; j *= nums[i - 1];
}
for (int i = n, j = 1; i >= 1; i--) {
ans[i - 1] *= j; j *= nums[i - 1];
}
return ans;
}
};

Python 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
ans = [1] * n
j = 1
for i in range(1, n + 1):
ans[i - 1] *= j
j *= nums[i - 1]
j = 1
for i in range(n, 0, -1):
ans[i - 1] *= j
j *= nums[i - 1]
return ans

TypeScript 代码:
1
2
3
4
5
6
7
8
9
10
11
function productExceptSelf(nums: number[]): number[] {
const n = nums.length;
const ans = new Array(n).fill(1);
for (let i = 1, j = 1; i <= n; i++) {
ans[i - 1] *= j; j *= nums[i - 1];
}
for (let i = n, j = 1; i >= 1; i--) {
ans[i - 1] *= j; j *= nums[i - 1];
}
return ans;
};

  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(1)$

最后

这是我们「刷穿 LeetCode」系列文章的第 No.238 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。