题目描述
给你一个整数数组 nums,返回数组 answer,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。要求不使用除法,且在 O(n) 时间复杂度内完成。
示例 1:
输出: [24,12,8,6]
解释:
- answer[0] = 2×3×4 = 24
- answer[1] = 1×3×4 = 12
- answer[2] = 1×2×4 = 8
- answer[3] = 1×2×3 = 6
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
解释:
- 存在 0 元素时,除自身外其他元素的乘积为 0
- answer[2] 对应位置无 0,乘积为 (-1)×1×(-3)×3 = 9
算法思路
两次遍历法:通过两次遍历分别计算每个元素的左边乘积和右边乘积,最终合并结果。
核心思想
- 左乘积遍历:从左到右遍历数组,计算每个元素左侧所有元素的乘积,存入结果数组。
- 右乘积遍历:从右到左遍历数组,计算每个元素右侧所有元素的乘积,并与左乘积相乘得到最终结果。
具体步骤
- 初始化结果数组:
ans[i]初始为左侧所有元素的乘积。 - 第一次遍历(左→右):
ans[i] = ans[i-1] * nums[i-1],逐步累乘左侧元素。
- 第二次遍历(右→左):
- 用临时变量
tmp存储右侧元素的累乘结果。 ans[i] *= tmp,将左右乘积合并。
- 用临时变量
复杂度分析
- 时间复杂度:O(n),两次线性遍历数组。
- 空间复杂度:O(1)(输出数组不计入空间复杂度)。
代码实现
func productExceptSelf(nums []int) []int {
n := len(nums)
if n == 0 {
return []int{}
}
ans := make([]int, n)
ans[0] = 1 // 第一个元素左侧无元素,乘积为1
// 计算左侧乘积
for i := 1; i < n; i++ {
ans[i] = ans[i-1] * nums[i-1]
}
tmp := 1 // 临时变量存储右侧乘积
// 计算右侧乘积并合并结果
// 左侧乘积计算完成后,最后一位已经确定,所以要从倒数第二位开始更新,也就是 n - 2
for i := n-2; i >= 0; i-- {
tmp *= nums[i+1]
ans[i] *= tmp
}
return ans
}
关键点总结
-
两次遍历策略:
- 左遍历计算每个元素左侧的累积乘积。
- 右遍历动态计算右侧乘积并直接合并到结果中。
-
空间优化:
- 利用结果数组
ans存储中间结果,避免使用额外空间。 - 通过临时变量
tmp减少右侧乘积的计算开销。
- 利用结果数组
-
边界处理:
- 数组长度为0时直接返回空数组。
- 第一个元素的左侧乘积初始化为1(无左侧元素)。
示例解析
示例1:nums = [1,2,3,4]
- 左遍历:
ans = [1, 1×1=1, 1×2=2, 2×3=6]→[1,1,2,6]
- 右遍历:
- i=2: tmp=4 → ans[2]=2×4=8
- i=1: tmp=4 × 3=12 → ans[1]=1×12=12
- i=0: tmp=12 × 2=24 → ans[0]=1×24=24
- 最终结果:
[24,12,8,6]
示例2:nums = [-1,1,0,-3,3]
- 左遍历:
ans = [1, -1, -1×1=-1, -1×0=0, 0×(-3)=0]
- 右遍历:
- i=3: tmp=3 → ans[3]=0×3=0
- i=2: tmp=3 × (-3)=-9 → ans[2]=-1 × (-9)=9
- i=1: tmp=-9 × 0=0 → ans[1]=-1 × 0=0
- i=0: tmp=0 × 1=0 → ans[0]=1 × 0=0
- 最终结果:
[0,0,9,0,0]