1. 问题背景与核心需求
今天遇到一个有趣的算法问题:给定一个整数数组nums,要求返回一个新数组answer,其中answer[i]等于nums中除nums[i]之外所有元素的乘积。题目要求不能使用除法运算,并且时间复杂度需要控制在O(n)内。
这个问题看似简单,但有几个关键约束条件:
- 不能使用除法运算(否则可以直接计算所有元素的乘积,然后除以当前元素)
- 需要O(n)时间复杂度(排除了暴力解法)
- 空间复杂度要求O(1)(不考虑输出数组的空间)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 暴力解法与问题分析
最直观的解法是暴力双重循环:
python复制def productExceptSelf(nums):
n = len(nums)
answer = [1] * n
for i in range(n):
for j in range(n):
if i != j:
answer[i] *= nums[j]
return answer
这种解法的时间复杂度是O(n²),显然不符合题目要求。我们需要找到一种更高效的方法。
3. 前缀与后缀乘积的概念
解决这个问题的关键在于理解前缀乘积和后缀乘积的概念:
- 前缀乘积:对于元素nums[i],其前缀乘积是指nums[0] × nums[1] × ... × nums[i-1]
- 后缀乘积:对于元素nums[i],其后缀乘积是指nums[i+1] × nums[i+2] × ... × nums[n-1]
那么,answer[i]实际上就是nums[i]的前缀乘积乘以后缀乘积。
4. 优化解法实现
我们可以通过两次遍历来计算前缀和后缀乘积:
4.1 第一次遍历:计算前缀乘积
从左到右遍历数组,计算每个位置的前缀乘积:
python复制def productExceptSelf(nums):
n = len(nums)
answer = [1] * n
# 计算前缀乘积
prefix = 1
for i in range(n):
answer[i] = prefix
prefix *= nums[i]
4.2 第二次遍历:计算后缀乘积并合并结果
从右到左遍历数组,计算每个位置的后缀乘积,并乘以前面计算的前缀乘积:
python复制 # 计算后缀乘积并合并结果
suffix = 1
for i in range(n-1, -1, -1):
answer[i] *= suffix
suffix *= nums[i]
return answer
这样,我们只需要两次遍历,时间复杂度为O(n),空间复杂度为O(1)(不考虑输出数组)。
5. 边界条件与特殊处理
在实际实现时,需要考虑一些边界条件:
- 数组长度为0或1的情况
- 数组中包含0的情况
- 数组中包含负数的情况
对于包含0的情况,我们需要特别注意:
- 如果数组中有超过一个0,那么所有结果都将是0
- 如果数组中只有一个0,那么只有该位置的乘积不为0
6. 算法复杂度分析
- 时间复杂度:O(n),因为我们只进行了两次线性遍历
- 空间复杂度:O(1),除了输出数组外,我们只使用了常数个额外变量
7. 实际应用场景
这种前缀+后缀乘积的算法在实际中有多种应用:
- 图像处理中的卷积运算
- 信号处理中的滑动窗口计算
- 统计学中的累积分布计算
- 金融领域中的累积收益率计算
8. 变种与扩展问题
基于这个基本问题,可以衍生出一些变种问题:
- 允许使用除法运算的解法
- 多维数组的乘积计算
- 流式数据中的乘积计算(数据不断到来)
- 乘积的绝对值或对数形式
9. 代码实现与测试
完整的Python实现如下:
python复制def productExceptSelf(nums):
n = len(nums)
answer = [1] * n
# 计算前缀乘积
prefix = 1
for i in range(n):
answer[i] = prefix
prefix *= nums[i]
# 计算后缀乘积并合并结果
suffix = 1
for i in range(n-1, -1, -1):
answer[i] *= suffix
suffix *= nums[i]
return answer
# 测试用例
print(productExceptSelf([1,2,3,4])) # 输出: [24,12,8,6]
print(productExceptSelf([-1,1,0,-3,3])) # 输出: [0,0,9,0,0]
10. 性能优化与注意事项
在实际应用中,我们可以考虑以下优化点:
- 对于大型数组,可以考虑并行计算前缀和后缀乘积
- 如果数组中有多个0,可以提前终止计算
- 对于浮点数数组,需要注意数值溢出问题
- 在内存受限的环境中,可以考虑原地修改输入数组
注意:在实际编码面试中,除了写出正确的代码外,还需要清楚地解释算法思路和复杂度分析。
