一、核心算法思想
题目:给定数组,返回新数组answer,answer[i]等于数组除nums[i]之外其余全部元素的乘积。禁止使用除法,时间复杂度 O (n) 。
公式:answer[i] = i位置左侧所有元素乘积 × i位置右侧所有元素乘积
- 第一趟从左向右遍历,在 ans 数组上直接保存左侧前缀乘积。
ans[0]=1,第一个元素左边没有数字。 - 第二趟从右向左遍历,用一个变量
right滚动维护右侧后缀乘积。 ans[i] = ans[i] * right,把左侧乘积和右侧乘积相乘,得到最终结果。- 更新
right,给左边下一个下标使用。
不开额外后缀数组,额外空间 O (1),输出数组不计入空间开销。
二、完整带注释代码
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] ans = new int[n];
// 第一轮:计算每个下标 左侧所有元素的乘积
ans[0] = 1; // 第0位左边没有元素,左侧乘积为1
for(int i = 1; i < n; i++){
ans[i] = ans[i-1] * nums[i-1];
}
// 第二轮:从右往左滚动计算后缀乘积
int right = 1; // 最右边元素右侧没有元素,后缀乘积初始为1
for(int i = n-1; i >= 0; i--){
ans[i] = ans[i] * right; // 左侧乘积 * 右侧乘积,得到答案
right = right * nums[i]; // 更新后缀乘积,供左边i-1使用
}
return ans;
}
}
三、实例推演
输入:nums = [1,2,3,4]
-
左向遍历(求前缀乘积)
i nums[i] ans [i] 含义(左侧乘积) ans 数组 0 1 左边无元素 [1,_,_,_]1 2 左边 [1] →1 [1,1,_,_]2 3 左边 [1,2] →2 [1,1,2,_]3 4 左边 [1,2,3] →6 [1,1,2,6] -
右向遍历,
right=1i ans[i] right (右侧乘积) ans [i]= 左 * right 更新 right 3 6 1 6*1=6 right=1*4=4 2 2 4 2*4=8 right=4*3=12 1 1 12 1*12=12 right=12*2=24 0 1 24 1*24=24 right=24*1=24
最终输出:[24,12,8,6]
四、语法 & 内置函数、边界坑
- ❌暴力嵌套循环 O (n²),本题数据规模(10^5),直接超时,不能用 List 做删除模拟。
- 顺序不能颠倒:必须
先算ans[i],再更新right。如果先更新 right,会把nums[i]自身算进乘积,结果出错。 - 初始化:
ans[0]=1、right=1。没有元素的时候乘积是 1(乘法单位元),不能初始化为 0。 - 输出数组长度必须等于原数组长度 n,不要写成 n‑1,否则数组越界。
- 题目限制不能用除法,如果数组包含 0,除法方案直接失效,这也是本题考点。
本题没有使用特殊内置 API,全部是数组基础遍历。
五、个人感悟
- 遇到不能用除法的乘积问题,优先想到前缀 + 后缀乘积拆分。
- 可以利用输出数组原地存储前缀,再用单个变量滚动后缀,节省内存,不需要开两个大数组。
- 滚动变量的顺序是高频踩坑点:先用旧值计算结果,再更新变量,很多双指针、后缀遍历题目都遵守这个规律。