

要解决"除自身以外数组的乘积"问题,需在 O(n) 时间复杂度 内完成计算,且不使用除法(避免除数为0等边界问题)。核心思路是利用 前缀积 和 后缀积 的组合,将时间复杂度优化至线性。
步骤1:理解问题本质
给定整数数组 nums,需构造结果数组 answer,使得 answer[i]等于 nums中除 nums[i]自身外所有元素的乘积。
例如:
- 若
nums = [1,2,3,4],则answer[0] = 2×3×4 = 24,answer[1] = 1×3×4 = 12,依此类推。
步骤2:设计"前缀积 + 后缀积"策略
直接暴力枚举每个元素、再遍历其余元素相乘(时间复杂度 O(n²))会超时,因此需用空间换时间:
-
前缀积数组
f:f[i]表示nums中 0, i-1 区间的元素乘积(即nums[i]左侧所有元素的乘积)。 -
后缀积数组
g:g[i]表示nums中 i+1, n-1 区间的元素乘积(即nums[i]右侧所有元素的乘积)。
最终 answer[i] = f[i] × g[i](左侧乘积 × 右侧乘积,恰好是"除自身外所有元素的乘积")。
步骤3:推导数组递推关系
(1)前缀积数组 f的构造
-
初始化:
f[0] = 1(区间[0, -1]无元素,乘积视为1)。 -
递推公式:
f[i] = f[i-1] × nums[i-1]。解释:
f[i]是"前i个元素的乘积",需基于f[i-1](前i-1个元素的乘积)再乘以第i-1个元素(nums[i-1])。
(2)后缀积数组 g的构造
-
初始化:
g[n-1] = 1(区间[n, n-1]无元素,乘积视为1)。 -
递推公式:
g[i] = g[i+1] × nums[i+1]。解释:
g[i]是"从i到末尾的元素乘积",需基于g[i+1](从i+1到末尾的乘积)再乘以第i+1个元素(nums[i+1])。
步骤4:代码实现(以Java为例)
通过"前缀积 → 后缀积 → 结果合并"三步完成计算:
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] f = new int[n]; // 前缀积数组
int[] g = new int[n]; // 后缀积数组
int[] answer = new int[n];
// 1. 构造前缀积数组f
f[0] = 1;
for (int i = 1; i < n; i++) {
f[i] = f[i-1] * nums[i-1];
}
// 2. 构造后缀积数组g
g[n-1] = 1;
for (int i = n-2; i >= 0; i--) {
g[i] = g[i+1] * nums[i+1];
}
// 3. 计算结果:前缀积 × 后缀积
for (int i = 0; i < n; i++) {
answer[i] = f[i] * g[i];
}
return answer;
}
步骤5:复杂度分析
-
时间复杂度:O(n)。三次线性遍历(构造前缀积、构造后缀积、合并结果),每次遍历时间为 O(n),总时间为 O(3n) = O(n)。
-
空间复杂度 :O(n)(前缀积数组
f和后缀积数组g各占 O(n);若优化空间,可将后缀积复用结果数组,将空间复杂度降为 O(1),逻辑本质不变)。
进阶:空间复杂度优化至 O(1)(除结果数组外)
利用结果数组 answer先存前缀积 ,再用一个变量 suffix动态维护后缀积,直接在结果数组中计算最终值:
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] answer = new int[n];
// 1. 先将前缀积存入answer
answer[0] = 1;
for (int i = 1; i < n; i++) {
answer[i] = answer[i-1] * nums[i-1];
}
// 2. 用变量suffix存后缀积,从后往前更新answer
int suffix = 1;
for (int i = n-1; i >= 0; i--) {
answer[i] = answer[i] * suffix; // 前缀积 × 后缀积
suffix *= nums[i]; // 更新后缀积(下一个左元素需乘当前nums[i])
}
return answer;
}
核心思想总结
通过前缀积 覆盖"目标元素左侧所有元素的乘积",后缀积覆盖"目标元素右侧所有元素的乘积",两者相乘即为结果。这种方法避免了暴力枚举的高时间复杂度,同时利用数组的线性遍历特性,确保了效率与可读性。