一、问题概述
给定一个整数数组 nums,返回一个数组 answer,其中 answer[i] 等于 nums 中除 nums[i] 之外所有元素的乘积。
- 要求:不能使用除法,且时间复杂度为 O (n)、空间复杂度为 O (1)(返回数组不计入额外空间)。
- 示例:输入
[1,2,3,4]→ 输出[24,12,8,6](24=2×3×4,12=1×3×4,8=1×2×4,6=1×2×3)。
二、代码核心思路
要计算 "除自身外所有元素的乘积",可以把这个乘积拆成两部分:
- 左乘积:当前元素左边所有元素的乘积(比如 nums [2]=3 的左乘积是 1×2)。
- 右乘积:当前元素右边所有元素的乘积(比如 nums [2]=3 的右乘积是 4)。
- 最终结果 = 左乘积 × 右乘积。
代码分两步实现:
- 先遍历数组,计算每个元素的右乘积 并存储到
answer数组中; - 再反向遍历数组,计算每个元素的左乘积,并与已存储的右乘积相乘,得到最终结果。
代码逐段解释
第一步:计算右乘积(从后往前遍历)
cpp
vector<int> answer(nums.size(),1); // 初始化结果数组,所有元素为1
int nums_add = 1; // 临时变量,存储累积的右乘积
for(int i=nums.size()-1;i>0;i--)
{
nums_add *= nums[i]; // 累积右乘积(从最后一个元素开始)
answer[i-1] = nums_add; // 把当前累积的右乘积存入前一个位置
}
关键:
- 初始化
answer为全 1 数组,因为 "没有元素相乘" 的默认乘积是 1(乘法单位元)。 - 从数组末尾向前遍历(i 从 n-1 到 1),
nums_add不断乘以当前元素nums[i],得到的是nums[i]到nums[n-1]的乘积(即nums[i-1]的右乘积)。 - 示例(nums=[1,2,3,4]):
- i=3(nums [3]=4):nums_add=1×4=4 → answer [2]=4(nums [2]=3 的右乘积是 4)。
- i=2(nums [2]=3):nums_add=4×3=12 → answer [1]=12(nums [1]=2 的右乘积是 12)。
- i=1(nums [1]=2):nums_add=12×2=24 → answer [0]=24(nums [0]=1 的右乘积是 24)。
- 第一步结束后,answer = [24,12,4,1]。
第二步:计算左乘积并合并结果(从前往后遍历)
cpp
nums_add = 1; // 重置临时变量,存储累积的左乘积
for(int i=1;i<nums.size();i++)
{
nums_add *= nums[i-1]; // 累积左乘积(从第一个元素开始)
answer[i] *= nums_add; // 左乘积 × 已存储的右乘积 = 最终结果
}
return answer;
关键解释:
- 重置
nums_add为 1,开始累积左乘积(从 nums [0] 开始)。 - 从 i=1 开始遍历,
nums_add不断乘以nums[i-1],得到的是nums[0]到nums[i-1]的乘积(即nums[i]的左乘积)。 - 把左乘积与第一步存储的右乘积相乘,得到最终的 "除自身外所有元素的乘积"。
- 示例(接第一步 answer=[24,12,4,1]):
- i=1:nums_add=1×1=1 → answer [1]=12×1=12(nums [1]=2 的左乘积 1,右乘积 12)。
- i=2:nums_add=1×2=2 → answer [2]=4×2=8(nums [2]=3 的左乘积 2,右乘积 4)。
- i=3:nums_add=2×3=6 → answer [3]=1×6=6(nums [3]=4 的左乘积 6,右乘积 1)。
- 第二步结束后,answer = [24,12,8,6](正确结果)。
三、完整执行过程
以输入 nums = [1,2,3,4] 为例:
| 步骤 | 变量 / 数组状态 | 说明 |
|---|---|---|
| 初始化 | answer = [1,1,1,1],nums_add=1 | 无 |
| 第一步 i=3 | nums_add=4,answer=[1,1,4,1] | nums[3]=4 → answer[2]=4 |
| 第一步 i=2 | nums_add=12,answer=[1,12,4,1] | nums[2]=3 → answer[1]=12 |
| 第一步 i=1 | nums_add=24,answer=[24,12,4,1] | nums[1]=2 → answer[0]=24 |
| 第二步重置 | nums_add=1 | 无 |
| 第二步 i=1 | nums_add=1,answer=[24,12,4,1] | nums[0]=1 → answer[1]=12×1 |
| 第二步 i=2 | nums_add=2,answer=[24,12,8,1] | nums[1]=2 → answer[2]=4×2 |
| 第二步 i=3 | nums_add=6,answer=[24,12,8,6] | nums[2]=3 → answer[3]=1×6 |
| 返回结果 | answer = [24,12,8,6] | 最终结果 |
四、注意事项
- 初始化细节 :
answer数组必须初始化为全 1,因为乘法的 "空乘积" 是 1(比如数组第一个元素的左乘积、最后一个元素的右乘积都是 1)。 - 遍历边界 :
- 第一步遍历到
i>0(即 i 从 n-1 到 1),避免越界(answer [i-1] 最小到 answer [0]); - 第二步遍历从
i=1开始,因为第一个元素的左乘积是 1,第一步已经算出其右乘积,无需再处理。
- 第一步遍历到
- 空间复杂度 :仅使用了一个临时变量
nums_add,返回数组不计入额外空间,满足 O (1) 要求。 - 无除法限制:全程用乘法累积,符合不能用除法的要求。
五、总结
- 核心思想:将 "除自身外乘积" 拆分为左乘积 × 右乘积,分两次遍历分别计算,避免重复运算。
- 关键细节:初始化全 1 数组、控制遍历边界、复用结果数组存储中间值,最大化节省空间。