
本文详解 leetcode 第 238 题「除自身以外数组的乘积」的最优解法——双遍历前缀/后缀积法,通过两次线性扫描在 o(1) 额外空间(不计输出数组)内完成计算,并逐行剖析执行逻辑与易错点。
本文详解 leetcode 第 238 题「除自身以外数组的乘积」的最优解法——双遍历前缀/后缀积法,通过两次线性扫描在 o(1) 额外空间(不计输出数组)内完成计算,并逐行剖析执行逻辑与易错点。
「除自身以外数组的乘积」是一道考察空间优化与数学思维的经典题目。题目要求:给定整数数组 nums,返回数组 answer,其中 answer[i] 等于 nums 中除 nums[i] 外所有元素的乘积;禁止使用除法,且需满足时间复杂度 O(n)、空间复杂度 O(1)(输出数组不计入空间复杂度)。
核心思想是:对每个位置 i,其结果 = 左侧所有元素乘积 × 右侧所有元素乘积。我们用两次遍历分别构建这两部分:
✅ 第一遍(从左到右):填充左侧乘积
初始化 pre = 1,ans[i] 存储 nums[0] × nums[1] × … × nums[i−1](即 i 左侧累积积):
-
i = 0:ans[0] = pre = 1→pre = 1 × nums[0] = 1 -
i = 1:ans[1] = pre = 1→pre = 1 × nums[1] = 2 -
i = 2:ans[2] = pre = 2→pre = 2 × nums[2] = 6 -
i = 3:ans[3] = pre = 6→pre = 6 × nums[3] = 24
→ 第一遍结束:ans = [1, 1, 2, 6]
⚠️ 注意:此处常见误区是误认为 ans 在第一轮就得到 [1,2,6,24] —— 实际上,ans[i] 赋值发生在 pre 更新之前,因此它永远代表“当前索引左侧”的积,不包含自身。
✅ 第二遍(从右到左):累乘右侧乘积
初始化 post = 1,从末尾反向遍历,每次将 ans[i] 乘以当前 post(即 nums[i+1] × … × nums[n−1]),再更新 post:
-
i = 3:ans[3] *= post → 6 × 1 = 6;post = 1 × nums[3] = 4 -
i = 2:ans[2] *= post → 2 × 4 = 8;post = 4 × nums[2] = 12 -
i = 1:ans[1] *= post → 1 × 12 = 12;post = 12 × nums[1] = 24 -
i = 0:ans[0] *= post → 1 × 24 = 24;post = 24 × nums[0] = 24
→ 最终:ans = [24, 12, 8, 6]✅
以下是完整可运行代码(含注释):
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] ans = new int[n];
// 第一次遍历:计算每个位置左侧乘积
int pre = 1;
for (int i = 0; i = 0; i--) {
ans[i] *= post; // ans[i] *= nums[i+1] * ... * nums[n-1]
post *= nums[i]; // 更新 post 为包含 nums[i] 的右侧积
}
return ans;
}
}
? 关键总结:
- 时间复杂度:O(n),仅两次独立遍历;
- 空间复杂度:O(1) 额外空间(
pre/post为常量变量,ans为必需输出); - 安全性:完全规避除零风险与溢出假设(题目未限定数值范围,但该解法天然兼容);
- 扩展性:思路可迁移至「子数组乘积小于 K」「滑动窗口积」等变种问题。
掌握这一“分治式累积”思想,不仅能高效解题,更是理解前缀和/前缀积类算法范式的坚实一步。










