题目描述
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 不要使用除法, 且在 O(n) 时间复杂度内完成此题。
示例:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
提示:
2 <= nums.length <= 105-30 <= nums[i] <= 30- 输入 保证 数组
answer[i]在 32 位 整数范围内
进阶: 你可以在 O(1) 的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组 不被视为 额外空间。)
解法
前缀积和后缀积
/**
* @param {number[]} nums
* @return {number[]}
*/
var productExceptSelf = function(nums) {
const n = nums.length;
const pre = Array(n);
pre[0] = 1;
for (let i = 1;i<n;++i){
pre[i] = pre[i-1] * nums[i-1];//i左边的所有乘积
}
let suf = 1;
for(let i = n-1;i>=0;--i){
pre[i] *= suf;
suf *= nums[i];
}
return pre;
};
题目求answer[i]为除i外数组其它所有元素的乘积,那么answer[i]可分解为:
answer[i] = pre[i] * suf[i]
-
pre[i]定义为i前面的所有元素的乘积 -
suf[i]定义为i后面的所有元素的乘积
即answer[i]等于i前面的所有元素的乘积乘以i后面所有元素的乘积
初始化pre[0]=suf[n-1]=0
因为nums[0]前面没有任何元素,nums[n-1]后面没有任何元素,而answer[i] = pre[i] * suf[i].当i=0时,很明显,answer[0] = suf[0],那么pre[0]应该初始化为1,同理suf[n-1]=1
所以我们的算法实现可以分为三部分:
- 求i的前缀积
- 求i的后缀积
- 求answer[i]
这三步每步的时间复杂度都为O(n),总体复杂度O(3n),即O(n)
我们可以进一步优化,将后两步放在一个循环中实现,即先求出前缀积,然后在求后缀积的循环过程中同时求结果
具体实现:
1. 第一次遍历:计算前缀积(左边积)
遍历数组,将每个 左侧所有元素的乘积计算出来,并存储到输出数组 pre[i] 中。
-
从左向右迭代。
- 初始化为 。
- 对于 , 等于前一个位置的前缀积 乘以 。
pre[0] = 1;
for (let i = 1; i < n; ++i) {
pre[i] = pre[i - 1] * nums[i - 1];
}
2. 第二次遍历:计算后缀积并求最终结果
从右向左迭代,同时利用变量 suf 实时计算后缀积,并将其乘入已存储在前缀积。
-
初始化变量
suf = 1(代表 右侧的乘积)。 -
从右向左遍历:
- 求结果: 将当前 (左边积)乘以当前的
suf(右边积),得到最终结果 。 - 更新后缀积: 将
suf乘上当前元素 ,使其包含 ,为下一个元素 的右边积做准备。
- 求结果: 将当前 (左边积)乘以当前的
let suf = 1;
for(let i = n - 1; i >= 0; --i) {
pre[i] *= suf; // 乘以当前维护的后缀积
suf *= nums[i]; // 更新后缀积,用于下一轮 i-1 的计算
}
时间,空间复杂度
时间复杂度:
两个for循环,O(2N),总体复杂度为O(N)
空间复杂度:
输出数组 不被视为 额外空间,仅使用了常数额外空间