持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情
题目(Product of Array Except Self)
链接:https://leetcode-cn.com/problems/product-of-array-except-self
解决数:1397
通过率:74.2%
标签:数组 前缀和
相关公司:facebook amazon lyft
给你一个整数数组 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) 的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
思路
- 从头遍历求出 左边的元素的积,从尾遍历求右边元素的积,分别存到 2 个数组中
- 再遍历一次,求出 output 数组的项
- 一共 3 次循环,时间复杂度是 O(3n),依然是O(n)
- 但是空间复杂度不满足要求
代码 优化前
var productExceptSelf = (nums) => {
const N = nums.length
const left_output = [] // 存放左边积
const right_output = [] // 存放右边积
const output = [] // 结果数组
left_output[0] = 1 // nums数组第一项没有左边项,初始化为1
right_output[N - 1] = 1 // nums数组最右项没有右边项,初始化为1
for (let i = 1; i < N; i++) { // 遍历求出每个元素的左边元素之积
left_output[i] = left_output[i - 1] * nums[i - 1] // 累乘一项即可
}
for (let i = N - 2; i >= 0; i--) { // 遍历求出每个元素的右边元素之积
right_output[i] = right_output[i + 1] * nums[i + 1] // 累乘一项即可
}
for (let i = 0; i < N; i++) { // 遍历求出output[i]
output[i] = left_output[i] * right_output[i]
}
return output
}
空间优化——怎么存左边积和右边积更好?
- 我们是否真的需要创建2个数组去保存左边积右边积?
- 其实 左边积、右边积 的计算,都是中间计算,没有留存的必要
- 可以让 output 数组先存右边积,然后被覆盖掉
- 右边积用一个变量保存,在遍历中被使用,用完就更新为新的右边积
利用好 output 数组空间,并引入变量保存右边积
- 从左遍历 nums ,每个元素的左边积存到 output 数组
- 从右遍历 nums ,一个变量保存了当前元素的右边积,让它和当前 output[i] 相乘(即当前的左边积),结果值覆盖到 output[i]
- 然后更新右边积,在下一次迭代中使用
- 每次迭代,做了两件事:求 左边积x右边积 & 更新右边积
优化后
var productExceptSelf = (nums) => {
const N = nums.length
const output = []
output[0] = 1
for (let i = 1; i < N; i++) { // output[i]是nums[i]的左边积
output[i] = output[i - 1] * nums[i - 1]
}
let right_output = 1 // 保存nums[i]的左边积
for (let i = N - 1; i >= 0; i--) {
output[i] *= right_output // 左边积 乘上 右边积
right_output *= nums[i] // 更新右边积
}
return output
}