LeetCode数组乘积问题:O(n)时间复杂度解法详解

发布时间:2026/10/26 13:02:30
LeetCode数组乘积问题:O(n)时间复杂度解法详解 1. 问题解析LeetCode Hot100 T238 数组乘积问题这道题目要求我们实现一个算法计算数组中每个元素除自身外其他所有元素的乘积。题目明确要求时间复杂度必须控制在O(n)不能使用除法运算空间复杂度除输出数组外应为O(1)1.1 核心难点分析这道题的难点主要在于三个限制条件的组合禁止使用除法运算排除了先计算总乘积再除以当前元素的取巧方案线性时间复杂度要求我们只能进行有限次数的数组遍历常数空间复杂度限制了额外数据结构的使用提示在实际面试中面试官提出这些限制条件是为了考察对前缀/后缀乘积的理解和应用能力。2. 解决方案设计与思路拆解2.1 双指针乘积法最优解采用左右乘积列表的思路通过两次遍历实现第一次从左到右遍历计算每个元素左侧所有元素的乘积第二次从右到左遍历计算每个元素右侧所有元素的乘积将左右乘积相乘得到最终结果def productExceptSelf(nums): n len(nums) res [1] * n # 计算左侧乘积 left_product 1 for i in range(1, n): left_product * nums[i-1] res[i] left_product # 计算右侧乘积并合并结果 right_product 1 for i in range(n-2, -1, -1): right_product * nums[i1] res[i] * right_product return res2.2 空间优化技巧虽然题目允许输出数组不计入空间复杂度但我们仍可以进一步优化使用输出数组直接存储左侧乘积在计算右侧乘积时使用单个变量动态维护右侧乘积def productExceptSelf(nums): n len(nums) res [1] * n # 左侧乘积直接存入res for i in range(1, n): res[i] res[i-1] * nums[i-1] # 右侧乘积使用变量维护 right 1 for i in range(n-1, -1, -1): res[i] * right right * nums[i] return res3. 算法复杂度分析3.1 时间复杂度无论采用哪种实现方式算法都只进行了两次线性遍历第一次遍历O(n)第二次遍历O(n)总时间复杂度O(2n) O(n)3.2 空间复杂度优化后的方案输出数组不计入空间复杂度仅使用常数个额外变量left, right等实际空间复杂度O(1)4. 边界条件与特殊测试用例4.1 必须考虑的边界情况空数组输入应返回空数组单元素数组根据题意应返回[1]包含0的数组单个0[0,1] → [1,0]多个0[0,0] → [0,0]4.2 典型测试用例示例test_cases [ ([1,2,3,4], [24,12,8,6]), ([-1,1,0,-3,3], [0,0,9,0,0]), ([0,0], [0,0]), ([1], [1]), ([], []) ]5. 同类问题扩展与变种5.1 允许使用除法的解法如果取消除法限制可以这样实现def productExceptSelf(nums): total 1 zero_count 0 for num in nums: if num 0: zero_count 1 continue total * num res [] for num in nums: if zero_count 1: res.append(0) elif zero_count 1: res.append(0 if num ! 0 else total) else: res.append(total // num) return res5.2 三维乘积问题扩展考虑三维数组版本计算每个元素在三个维度上的乘积def product3DExceptSelf(matrix): m, n, p len(matrix), len(matrix[0]), len(matrix[0][0]) res [[[1 for _ in range(p)] for _ in range(n)] for _ in range(m)] # 计算x方向乘积 for i in range(m): for j in range(n): for k in range(p): if i 0: res[i][j][k] * res[i-1][j][k] * matrix[i-1][j][k] # 计算y方向乘积 # ...类似实现其他维度... return res6. 实际工程应用场景6.1 图像处理中的滤波器计算在图像卷积运算中经常需要计算局部窗口内像素的乘积这种算法可以优化某些特殊滤波器的实现。6.2 金融数据分析计算投资组合中各资产的相对表现时可能需要排除特定资产计算其余资产的综合表现。6.3 推荐系统在协同过滤算法中计算用户相似度时可能需要排除当前物品的影响。7. 常见错误与调试技巧7.1 典型错误模式初始化错误忘记将res[0]初始化为1错误地使用[0]*n作为初始值边界处理不当在第二次遍历时错误的起始索引处理n-1元素时数组越界乘积顺序错误先计算右侧乘积再计算左侧乘积导致覆盖7.2 调试建议使用小规模测试用例手动验证打印中间乘积结果检查计算过程特别注意索引边界条件对于包含0的数组要单独测试8. 不同语言实现要点8.1 C实现注意事项vectorint productExceptSelf(vectorint nums) { int n nums.size(); vectorint res(n, 1); int left 1; for(int i 1; i n; i) { left * nums[i-1]; res[i] left; } int right 1; for(int i n-2; i 0; --i) { right * nums[i1]; res[i] * right; } return res; }注意点使用vector时要预先分配空间注意整数溢出问题可能需要使用long long8.2 Java实现特点public int[] productExceptSelf(int[] nums) { int n nums.length; int[] res new int[n]; Arrays.fill(res, 1); int left 1; for(int i 1; i n; i) { left * nums[i-1]; res[i] left; } int right 1; for(int i n-2; i 0; i--) { right * nums[i1]; res[i] * right; } return res; }特点使用Arrays.fill初始化数组注意Java数组是固定长度的9. 算法优化进阶思路9.1 并行计算优化对于超大规模数组可以考虑将数组分段并行计算各段的左右乘积合并分段结果9.2 流式处理版本适合数据流场景维护动态乘积class StreamProduct: def __init__(self): self.products [] self.total 1 def add(self, num): self.products.append(self.total) self.total * num def getProductExceptSelf(self, index): return self.products[index] * (self.total // (self.products[index] * self.nums[index]))10. 面试技巧与答题策略10.1 面试回答框架明确问题要求和限制条件提出暴力解法并分析其缺点逐步优化思路解释前缀/后缀乘积的概念给出优化后的实现方案分析时间/空间复杂度讨论边界条件和测试用例10.2 可能follow-up问题如果数组特别大无法全部放入内存怎么办如何修改算法使其适用于链表结构如果允许使用除法算法会有怎样的变化如何测试这个函数的正确性11. 数学原理深入探讨11.1 乘积的分解性质该算法基于乘积的分解性质 product left_product * right_product 其中 left_product ∏ nums[0..i-1] right_product ∏ nums[i1..n-1]11.2 前缀与后缀的数学关系本质上是在计算 res[i] prefix[i-1] * suffix[i1] 其中prefix和suffix可以通过动态规划递推得到12. 可视化理解方法12.1 图形化表示以数组[1,2,3,4]为例原始数组 [1, 2, 3, 4] 左乘积 [1, 1, 2, 6] 右乘积 [24, 12, 4, 1] 结果 [24, 12, 8, 6]12.2 动画演示思路从左到右的扫描过程逐步累积左侧乘积从右到左的扫描过程逐步累积右侧乘积最后合并的过程13. 历史背景与相关算法13.1 问题起源这类乘积问题最早出现在信号处理领域用于计算滑动窗口内的乘积。在1980年代的图像处理论文中就有类似算法的记载。13.2 相关算法比较滑动窗口乘积维护一个固定大小的窗口乘积累加数组计算前缀和数组的变种分治算法将数组分成两半分别计算14. 实际编码注意事项14.1 Python实现细节列表初始化使用乘法要小心对于可变对象不适用注意Python的整数不会溢出但其他语言需要考虑使用range时注意边界14.2 防御性编程添加输入验证if not nums: return []处理超大数乘积虽然Python不需要添加类型注解提高可读性15. 单元测试与性能测试15.1 单元测试样例import unittest class TestProduct(unittest.TestCase): def test_normal_case(self): self.assertEqual(productExceptSelf([1,2,3,4]), [24,12,8,6]) def test_with_zeros(self): self.assertEqual(productExceptSelf([0,1,2]), [2,0,0]) def test_empty_input(self): self.assertEqual(productExceptSelf([]), [])15.2 性能测试方法import time import random def test_performance(): sizes [10, 100, 1000, 10000] for size in sizes: nums [random.randint(1, 100) for _ in range(size)] start time.time() productExceptSelf(nums) print(fSize {size}: {time.time()-start:.6f}s)16. 算法扩展应用16.1 对数变换解法利用对数的性质将乘积转换为对数和import math def productExceptSelf(nums): sum_log sum(math.log10(num) for num in nums) return [round(10**(sum_log - math.log10(num))) for num in nums]注意此方法存在浮点精度问题仅适用于特定场景16.2 位运算版本对于二进制数组元素仅为0或1def productExceptSelf(nums): total_and 1 for num in nums: total_and num return [total_and if num else 0 for num in nums]17. 多维度思考角度17.1 从动态规划视角可以将左右乘积看作两个状态left[i] left[i-1] * nums[i-1]right[i] right[i1] * nums[i1]17.2 从分治算法视角将数组分成两半前半部分的乘积 * 后半部分的乘积递归计算直到单个元素18. 内存访问模式分析18.1 缓存友好性该算法的两次遍历都是顺序访问具有很好的空间局部性对CPU缓存友好。18.2 内存占用分析优化后的版本输入数组O(n)输出数组O(n)通常不计入临时变量O(1)19. 相关LeetCode题目推荐乘积最大子数组最大子序和接雨水分发糖果三个数的最大乘积20. 个人实战经验分享在实际编码中发现几个易错点初始值设置不正确导致第一个或最后一个元素错误遍历方向错误导致乘积计算错误处理包含0的数组时逻辑不完整建议的编码步骤先写出暴力解法理解题意画出乘积计算示意图分步实现左右乘积计算最后合并优化

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询