LeetCode 238. 除了自身以外数组的乘积|最优解详解(O(n)时间+O(1)空间)

在 LeetCode 数组类题目中,238 题「除了自身以外数组的乘积」是一道经典的中等难度题,核心考察对时间、空间复杂度的优化能力,也是面试中的高频考点。题目要求在不使用除法、O(n) 时间复杂度内完成解题,同时最优解还能做到 O(1) 额外空间(除结果数组外)。本文将从题目分析入手,逐步拆解解题思路,深入解析最优代码,并拓展相关面试考点。

一、题目描述与核心约束

题目原文

给你一个整数数组 nums,返回 数组 answer ,其中 answeri 等于 nums 中除了 numsi 之外其余各元素的乘积。题目数据保证数组 nums 之中任意元素的全部前缀元素和后缀的乘积都在 32 位整数范围内。请不要使用除法,且在 O(n) 时间复杂度内完成此题。

核心约束

  • 时间复杂度:必须 O(n),不能用暴力遍历(O(n²) 会超时);

  • 空间复杂度:尽量优化,最优要求 O(1) 额外空间(结果数组不计入额外空间);

  • 禁用除法:避免处理 0 带来的逻辑复杂问题,同时满足题目硬性要求;

  • 数值范围:无需担心乘积溢出 32 位整数,题目已保证。

示例演示

输入:nums = 1,2,3,4 → 输出:24,12,8,6

输入:nums = -1,1,0,-3,3 → 输出:0,0,9,0,0

二、解题思路拆解

要在不使用除法的前提下,计算每个元素除外的乘积,核心思路是「拆分乘积来源」------ 每个元素的结果 = 左侧所有元素的乘积 × 右侧所有元素的乘积。基于这个核心,我们可以从基础思路逐步优化到最优解。

思路 1:前缀积 + 后缀积数组(过渡思路)

最直观的想法是用两个辅助数组,分别存储每个元素的前缀积(左侧乘积)和后缀积(右侧乘积),再将两者相乘得到结果:

  1. 前缀积数组 prefix:prefixi 表示 nums0 到 numsi-1 的乘积(即 numsi 左侧所有元素的积);

  2. 后缀积数组 suffix:suffixi 表示 numsi+1 到 numsn-1 的乘积(即 numsi 右侧所有元素的积);

  3. 结果 answeri = prefixi × suffixi

该思路时间复杂度 O(n)(三次线性遍历),但空间复杂度 O(n)(两个辅助数组),不符合「O(1) 额外空间」的优化要求,仅作为理解核心逻辑的过渡。

思路 2:两次遍历 + 结果数组复用(最优解)

核心优化点:复用结果数组存储前缀积,再用一个变量动态记录后缀积,反向遍历更新结果,彻底省去辅助数组的空间开销。

具体步骤拆解:

  1. 初始化结果数组 res,长度与 nums 一致;

  2. 正向遍历计算前缀积:用变量 leftProduct 记录当前元素左侧所有元素的乘积(初始为 1,因为第一个元素左侧无元素),将 leftProduct 赋值给 resi,再更新 leftProduct(乘上当前 numsi),此时 res 数组存储的是每个元素的左侧乘积;

  3. 反向遍历计算最终结果:用变量 rightProduct 记录当前元素右侧所有元素的乘积(初始为 1,因为最后一个元素右侧无元素),将 resi(左侧乘积)与 rightProduct(右侧乘积)相乘,更新 resi,再更新 rightProduct(乘上当前 numsi);

  4. 返回 res 数组,即为最终结果。

该思路仅用两次线性遍历,时间复杂度 O(n),除结果数组外无额外空间,空间复杂度 O(1),完全满足题目要求,也是面试中推荐的标准答案。

三、最优代码解析(TypeScript)

基于上述最优思路,结合边界处理(空数组),代码实现如下,逐行解析核心逻辑:

typescript 复制代码
function productExceptSelf(nums: number[]): number[] {
  const numsL = nums.length;
  // 边界处理:空数组直接返回空
  if (numsL === 0) {
    return [];
  }

  const res = new Array(nums.length);

  // 第一步:正向遍历,计算左侧乘积并存储到 res
  let leftProduct = 1; // 初始左侧乘积为 1(第一个元素左侧无元素)
  for (let i = 0; i < numsL; i++) {
    res[i] = leftProduct; // 先赋值左侧乘积
    leftProduct *= nums[i]; // 更新左侧乘积,包含当前元素,供下一个元素使用
  }

  // 第二步:反向遍历,计算右侧乘积并与左侧乘积相乘,更新 res
  let rightProduct = 1; // 初始右侧乘积为 1(最后一个元素右侧无元素)
  for (let i = numsL - 1; i >= 0; i--) {
    res[i] *= rightProduct; // 左侧乘积 × 右侧乘积 = 最终结果
    rightProduct *= nums[i]; // 更新右侧乘积,包含当前元素,供上一个元素使用
  }

  return res;
};
      

代码执行流程演示(以 nums = 1,2,3,4 为例)

  1. 正向遍历(计算左侧乘积):

    • i=0:leftProduct=1 → res0=1 → leftProduct=1×1=1;

    • i=1:leftProduct=1 → res1=1 → leftProduct=1×2=2;

    • i=2:leftProduct=2 → res2=2 → leftProduct=2×3=6;

    • i=3:leftProduct=6 → res3=6 → leftProduct=6×4=24;

    • 正向遍历后 res = 1,1,2,6

  2. 反向遍历(计算最终结果):

    • i=3:rightProduct=1 → res3=6×1=6 → rightProduct=1×4=4;

    • i=2:rightProduct=4 → res2=2×4=8 → rightProduct=4×3=12;

    • i=1:rightProduct=12 → res1=1×12=12 → rightProduct=12×2=24;

    • i=0:rightProduct=24 → res0=1×24=24 → rightProduct=24×1=24;

    • 反向遍历后 res = 24,12,8,6,与预期结果一致。

四、面试延伸与注意事项

1. 常见追问与避坑点

  • Q:为什么不能用除法?

    A:一方面是题目硬性要求,另一方面是除法存在两个致命问题:数组含 0 时除法无意义,需额外处理多个 0 的场景;存在数值溢出风险,且逻辑复杂度高于最优解。

  • Q:如何处理数组含 0 的情况?

    A:最优解无需额外处理!因为左侧/右侧乘积会自然包含 0 的影响(如示例 -1,1,0,-3,3 ,正向遍历后 res 存储左侧乘积,反向遍历结合右侧乘积,最终会自动得出 0,0,9,0,0 的结果)。

  • Q:空间复杂度 O(1) 的依据是什么?

    A:题目允许结果数组不计入额外空间,最优解仅用了两个变量(leftProduct、rightProduct),无其他辅助空间,因此额外空间复杂度为 O(1)。

2. 同类题目推荐

掌握本题思路后,可拓展练习以下同类题目,强化对「前缀/后缀积」的应用:

  • LeetCode 42. 接雨水(前缀最大值+后缀最大值思路);

  • LeetCode 152. 乘积最大子数组(前缀积+后缀积优化);

  • LeetCode 724. 寻找数组的中心下标(前缀和思路)。

五、总结

LeetCode 238 题的核心是「拆分乘积来源」,最优解通过「两次遍历 + 结果数组复用」,在 O(n) 时间、O(1) 额外空间内完成解题,既满足题目约束,又体现了空间优化的核心思想------复用已有空间,用变量替代辅助数组

这类题目在面试中常考察「思路优化能力」,从暴力法到过渡思路,再到最优解,逐步拆解的过程能帮助我们理解算法优化的本质。掌握该思路后,可快速迁移到前缀和、前缀最大值等同类问题中,提升解题效率。

相关推荐
自进化Agent智能体1 分钟前
MCP与Hooks:让AI Agent安全连接一切的治理框架
前端
memcpy02 分钟前
LeetCode 2144. 打折购买糖果的最小开销【贪心】
算法·leetcode·职场和发展
明天一点3 分钟前
Cloudflare 通知转发钉钉机器人
前端·后端
前端Hardy3 分钟前
前端日历组件,要变天了?Schedule-X v4.6 彻底杀疯了
前端·javascript·后端
微扬嘴角19 分钟前
React快速入门
前端·react.js·前端框架
ZC跨境爬虫36 分钟前
跟着 MDN 学CSS day_40:(Flexbox实战技能测试)
前端·css·ui·html·tensorflow
ZC跨境爬虫41 分钟前
跟着 MDN 学CSS day_36:(float、clear与BFC深度解析)
前端·javascript·css·ui·交互
ConardLi1 小时前
啊?我刚开源的 Skills 已经 7K Star 了?!
前端·人工智能·后端
糯米团子7491 小时前
javascript高频知识点
开发语言·前端·javascript
散峰而望1 小时前
【算法练习】算法练习精选:陶陶摘苹果(基础+升级)、Music Notes、字串变换,你能AC几道?
数据结构·c++·算法·leetcode·贪心算法·github·动态规划