除自身以外数组的乘积
题目描述:
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 不要使用除法,且在 O(n)
时间复杂度内完成此题。
示例 1:
输入: nums =[1,2,3,4]
输出:[24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3] 输出: [0,0,9,0,0]
提示:
2 <= nums.length <= 105
-30 <= nums[i] <= 30
- 保证 数组
nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内
进阶:你可以在 O(1)
的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组 不被视为 额外空间。)
方法一思路分析:
- 初始化左右乘积数组:
- 创建两个辅助数组
L
和R
,长度与输入数组nums
相同。L[i]
用于存储nums[i]
左侧所有元素的乘积,R[i]
用于存储nums[i]
右侧所有元素的乘积。
- 创建两个辅助数组
- 计算左侧乘积:
- 初始化
L[0]
为 1,因为第一个元素左侧没有元素。 - 从左到右遍历
nums
,计算每个位置的左侧乘积并存储在L
数组中。
- 初始化
- 计算右侧乘积:
- 初始化
R[length - 1]
为 1,因为最后一个元素右侧没有元素。 - 从右到左遍历
nums
,计算每个位置的右侧乘积并存储在R
数组中。
- 初始化
- 计算最终结果:
- 创建一个结果数组
answer
,长度为nums
的长度。 - 对于
nums
中的每个元素,其除了自身以外所有元素的乘积就是其左侧所有元素的乘积乘以右侧所有元素的乘积。即answer[i] = L[i] * R[i]
。
- 创建一个结果数组
- 返回结果:
- 返回
answer
数组作为最终结果。
- 返回
代码实现:
class Solution {
public int[] productExceptSelf(int[] nums) {
int length = nums.length;
// L 和 R 分别表示左右两侧的乘积列表
int[] L = new int[length];
int[] R = new int[length];
int[] answer = new int[length];
// L[i] 为索引 i 左侧所有元素的乘积
// 对于索引为 '0' 的元素,因为左侧没有元素,所以 L[0] = 1
L[0] = 1;
for (int i = 1; i < length; i++) {
L[i] = nums[i - 1] * L[i - 1];
}
// R[i] 为索引 i 右侧所有元素的乘积
// 对于索引为 'length-1' 的元素,因为右侧没有元素,所以 R[length-1] = 1
R[length - 1] = 1;
for (int i = length - 2; i >= 0; i--) {
R[i] = nums[i + 1] * R[i + 1];
}
// 对于索引 i,除 nums[i] 之外其余各元素的乘积就是左侧所有元素的乘积乘以右侧所有元素的乘积
for (int i = 0; i < length; i++) {
answer[i] = L[i] * R[i];
}
return answer;
}
}
方法二思路分析:
题目进阶要求在 O(1)
的额外空间复杂度内完成这个题目,且输出数组不算额外空间。所以可以考虑用一个变量代替数组的使用,变量为右侧所有元素的乘积。
- 计算每个元素左侧所有元素的乘积:
- 创建一个与原数组相同长度的新数组
answer
,用于存储结果。 - 初始化
answer[0]
为 1,因为第一个元素左侧没有其他元素。 - 从第二个元素开始遍历原数组,每个位置
i
的answer[i]
等于nums[i - 1]
乘以answer[i - 1]
。这样,answer[i]
就存储了原数组中索引i
左侧所有元素的乘积。
- 创建一个与原数组相同长度的新数组
- 计算每个元素右侧所有元素的乘积,并更新结果数组:
- 初始化一个变量
R
为 1,用于存储当前元素右侧所有元素的乘积。 - 从原数组的最后一个元素开始向左遍历。
- 对于每个元素,将其左侧乘积(即
answer[i]
)与右侧乘积R
相乘,得到的结果就是除了nums[i]
以外的所有元素的乘积,并更新answer[i]
。 - 更新
R
,将其乘以当前遍历到的元素nums[i]
,以便计算下一个元素的右侧乘积。
- 初始化一个变量
举一个具体的例子来说明:
假设我们有一个整数数组 nums = [1, 2, 3, 4]
。
-
计算每个元素左侧所有元素的乘积:
- 初始化结果数组
answer = [0, 0, 0, 0]
。 answer[0]
设置为 1,因为第一个元素左侧没有其他元素。- 计算
answer[1]
:answer[1] = nums[0] * answer[0] = 1 * 1 = 1
。 - 计算
answer[2]
:answer[2] = nums[1] * answer[1] = 2 * 1 = 2
。 - 计算
answer[3]
:answer[3] = nums[2] * answer[2] = 3 * 2 = 6
。
此时,
answer = [1, 1, 2, 6]
。这个数组存储了每个元素左侧所有元素的乘积。 - 初始化结果数组
-
计算每个元素右侧所有元素的乘积,并更新结果数组:
- 初始化变量
R = 1
,用于存储当前元素右侧所有元素的乘积。 - 从右向左遍历
nums
数组。 - 对于
nums[3]
(即 4):answer[3] = answer[3] * R = 6 * 1 = 6
,然后R = R * nums[3] = 1 * 4 = 4
。 - 对于
nums[2]
(即 3):answer[2] = answer[2] * R = 2 * 4 = 8
,然后R = R * nums[2] = 4 * 3 = 12
。 - 对于
nums[1]
(即 2):answer[1] = answer[1] * R = 1 * 12 = 12
,然后R = R * nums[1] = 12 * 2 = 24
。 - 对于
nums[0]
(即 1):answer[0] = answer[0] * R = 1 * 24 = 24
。
最终,
answer = [24, 12, 8, 6]
。这个数组就是除了自身以外所有元素的乘积。 - 初始化变量
代码实现:
class Solution {
public int[] productExceptSelf(int[] nums) {
int length = nums.length;
int[] answer = new int[length];
// answer[i] 表示索引 i 左侧所有元素的乘积
// 因为索引为 '0' 的元素左侧没有元素, 所以 answer[0] = 1
answer[0] = 1;
for (int i = 1; i < length; i++) {
answer[i] = nums[i - 1] * answer[i - 1];
}
// R 为右侧所有元素的乘积
// 刚开始右边没有元素,所以 R = 1
int R = 1;
for (int i = length - 1; i >= 0; i--) {
// 对于索引 i,左边的乘积为 answer[i],右边的乘积为 R
answer[i] = answer[i] * R;
// R 需要包含右边所有的乘积,所以计算下一个结果时需要将当前值乘到 R 上
R *= nums[i];
}
return answer;
}
}