news 2026/7/18 20:45:15

力扣hot100-238.除自身以外数组的乘积-前缀积后缀积详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
力扣hot100-238.除自身以外数组的乘积-前缀积后缀积详解

LeetCode 238. 除自身以外数组的乘积:前缀积 + 后缀积详解

1. 这道题属于什么算法思想

这题主要归类为:

数组 / 前缀积 + 后缀积

它和“前缀和”思想类似,但这里做的不是加法,而是乘法。

题目中每个位置的答案,都由两部分组成:

当前位置左边所有元素的乘积。 当前位置右边所有元素的乘积。

把这两部分相乘,就得到“除自身以外数组的乘积”。

2. 这道题到底在问什么

题目要求返回数组answer,满足:

answer[i] 等于 nums 中除了 nums[i] 之外其余所有元素的乘积。

例如:

nums = [1, 2, 3, 4]

结果是:

answer[0] = 2 * 3 * 4 = 24 answer[1] = 1 * 3 * 4 = 12 answer[2] = 1 * 2 * 4 = 8 answer[3] = 1 * 2 * 3 = 6

因此:

answer = [24, 12, 8, 6]

题目还有两个限制:

1. 不允许使用除法。 2. 要在 O(n) 时间内完成。

3. 为什么不能直接用总乘积除自身

最直观的想法是先计算所有元素的总乘积:

total = nums[0] * nums[1] * ... * nums[n - 1]

然后:

answer[i] = total / nums[i]

但题目明确禁止使用除法。

而且数组中可能有0

例如:

nums = [1, 2, 0, 4]

总乘积是:

0

如果计算answer[2],会得到:

0 / 0

这无法计算。

因此要换一种不依赖除法的方法。

4. 为什么不直接使用暴力解法

可以直接使用暴力解法,它是正确的。

思路是:对于每个下标i,重新遍历整个数组,跳过nums[i],把其余元素相乘。

classSolution{publicint[]productExceptSelf(int[]nums){intn=nums.length;int[]answer=newint[n];for(inti=0;i<n;i++){intproduct=1;for(intj=0;j<n;j++){// 当前答案不能乘 nums[i] 自己。if(i!=j){product*=nums[j];}}answer[i]=product;}returnanswer;}}

例如:

nums = [1, 2, 3, 4]

计算answer[0]时,需要计算:

2 * 3 * 4

计算answer[1]时,又需要计算:

1 * 3 * 4

其中:

3 * 4

这部分被重复计算了。随着数组变大,左边和右边的很多乘积都会反复计算。

时间复杂度是:

外层循环 n 次。 每次内层循环遍历 n 次。 总时间复杂度是 O(n^2)。

题目明确要求在O(n)时间内完成,因此暴力法不能作为最终提交解法。

前缀积和后缀积的作用,就是提前保存并复用这些重复乘积:

left[i] 保存 i 左边所有元素的乘积。 right[i] 保存 i 右边所有元素的乘积。

于是每个答案只需:

answer[i] = left[i] * right[i]

不再需要为每个i重新扫描整个数组。

5. 第一性原理:每个答案由左边和右边组成

对于nums[i],需要排除的只有它自己。

数组中其余元素可以自然拆成两部分:

nums[i] 左边的元素。 nums[i] 右边的元素。

因此:

answer[i] = 左边所有元素的乘积 * 右边所有元素的乘积

例如:

nums = [1, 2, 3, 4] 下标: 0 1 2 3

对于:

i = 2,nums[2] = 3

左边元素是:

1, 2

右边元素是:

4

所以:

answer[2] = (1 * 2) * 4 = 8

这就是整题核心:

每个位置不需要重新遍历整个数组,只需要知道它左边的乘积和右边的乘积。

6. 用 left 和 right 两个数组直接表达思路

定义:

left[i]:nums[i] 左边所有元素的乘积。 right[i]:nums[i] 右边所有元素的乘积。

那么:

answer[i] = left[i] * right[i]

对于:

nums = [1, 2, 3, 4]

6.1 left 数组

left[0]表示nums[0]左边所有元素的乘积。

nums[0]左边没有元素,因此:

left[0] = 1

为什么是1而不是0

因为乘法中的空乘积定义为1

它的作用是保证:

没有左边元素时,answer[0] 只由右边元素决定。 1 * 右边乘积 = 右边乘积。

之后从左到右计算:

left[1] = nums[0] = 1 left[2] = nums[0] * nums[1] = 1 * 2 = 2 left[3] = nums[0] * nums[1] * nums[2] = 1 * 2 * 3 = 6

所以:

left = [1, 1, 2, 6]

递推关系是:

left[i] = left[i - 1] * nums[i - 1]

原因是:

nums[i] 左边的元素 = nums[i - 1] 左边的元素,再加上 nums[i - 1] 自己。

6.2 right 数组

right[3]表示nums[3]右边所有元素的乘积。

nums[3]右边没有元素,因此:

right[3] = 1

之后从右到左计算:

right[2] = nums[3] = 4 right[1] = nums[2] * nums[3] = 3 * 4 = 12 right[0] = nums[1] * nums[2] * nums[3] = 2 * 3 * 4 = 24

所以:

right = [24, 12, 4, 1]

递推关系是:

right[i] = right[i + 1] * nums[i + 1]

原因是:

nums[i] 右边的元素 = nums[i + 1] 右边的元素,再加上 nums[i + 1] 自己。

6.3 合并 left 和 right

最后逐个位置相乘:

answer[0] = left[0] * right[0] = 1 * 24 = 24 answer[1] = left[1] * right[1] = 1 * 12 = 12 answer[2] = left[2] * right[2] = 2 * 4 = 8 answer[3] = left[3] * right[3] = 6 * 1 = 6

因此:

answer = [24, 12, 8, 6]

7. 用表格完整推导

对于:

nums = [1, 2, 3, 4]
inums[i]left[i]:左边乘积right[i]:右边乘积answer[i]
0112424
1211212
23248
34616

表格中每一行都满足:

answer[i] = left[i] * right[i]

8. 数组中有 0 时为什么仍然正确

前缀积和后缀积不使用除法,因此能自然处理0

例如:

nums = [1, 2, 0, 4]

正确答案应为:

answer = [0, 0, 8, 0]

解释:

answer[0] = 2 * 0 * 4 = 0 answer[1] = 1 * 0 * 4 = 0 answer[2] = 1 * 2 * 4 = 8 answer[3] = 1 * 2 * 0 = 0

计算数组:

left = [1, 1, 2, 0] right = [0, 0, 4, 1]

相乘:

[1 * 0, 1 * 0, 2 * 4, 0 * 1] = [0, 0, 8, 0]

结果正确。

如果有两个或更多个0,每个位置除自身外仍至少包含一个0,所以所有答案都是0。前缀积和后缀积同样会自然得到这个结果。

9. Java 代码完整注释

classSolution{publicint[]productExceptSelf(int[]nums){intn=nums.length;// left[i] 表示 nums[i] 左边所有元素的乘积。int[]left=newint[n];// right[i] 表示 nums[i] 右边所有元素的乘积。int[]right=newint[n];// answer[i] = left[i] * right[i]。int[]answer=newint[n];// nums[0] 左边没有元素,空乘积为 1。left[0]=1;// 从左到右计算每个位置左边的乘积。for(inti=1;i<n;i++){// nums[i] 左边的乘积// = nums[i - 1] 左边的乘积 * nums[i - 1]。left[i]=left[i-1]*nums[i-1];}// nums[n - 1] 右边没有元素,空乘积为 1。right[n-1]=1;// 从右到左计算每个位置右边的乘积。for(inti=n-2;i>=0;i--){// nums[i] 右边的乘积// = nums[i + 1] 右边的乘积 * nums[i + 1]。right[i]=right[i+1]*nums[i+1];}// 左边乘积 * 右边乘积,就是除 nums[i] 之外所有元素的乘积。for(inti=0;i<n;i++){answer[i]=left[i]*right[i];}returnanswer;}}

10. 复杂度分析

时间复杂度:

O(n)

分别计算leftrightanswer,每一轮都只遍历数组一次,总体仍是线性时间。

空间复杂度:

O(n)

除了题目要求返回的answer外,还使用了leftright两个长度为n的数组。

后续可以把leftright的信息复用到answer和一个普通变量中,从而优化额外空间;但这属于空间优化,不是理解本题的必要前提。

11. 总结

这题属于:

数组 / 前缀积 + 后缀积

核心公式是:

answer[i] = left[i] * right[i]

其中:

left[i] 表示 nums[i] 左边所有元素的乘积。 right[i] 表示 nums[i] 右边所有元素的乘积。

left[0]right[n - 1]都初始化为1,因为两侧没有元素时,空乘积应该是不改变结果的乘法单位元1

可以把整题记成一句话:

每个位置的答案,等于它左边所有数的乘积乘以它右边所有数的乘积。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/18 20:44:36

让Zotero变得更好的科研利器--小绿鲸

我发现了一个新的工具&#xff0c;可以让Zotero不再是单纯的管理工具&#xff0c;而是变成一个能读文献、做研究的科研利器。首先打开Zotero&#xff0c;找到设置&#xff0c;在常规里把PDF阅读器默认改成小绿鲸。然后随便打开一篇文献&#xff0c;它直接跳到了小绿鲸的阅读界面…

作者头像 李华
网站建设 2026/7/18 20:42:11

员工电脑如何监控?三种主流监控方法分享,哪个性能更强劲?

员工电脑监控不是新鲜事&#xff0c;但很多管理者对它要么讳莫如深&#xff0c;要么简单粗暴地装个软件就完事。其实从技术角度看&#xff0c;监控手段分好几个层面&#xff0c;各有优劣。这篇文章不讲虚的&#xff0c;直接说三种主流方法&#xff0c;再把其中一种工具的具体功…

作者头像 李华
网站建设 2026/7/18 20:41:28

服装店大货下单前,怎么判断供应商能不能稳定补货?

服装店做大货&#xff0c;最怕的不是单价高&#xff0c;而是压错了供应商。一批几百件的大货砸下去&#xff0c;结果交期拖延、补货断档、到货质量跟打样时两回事&#xff0c;整个销售节奏全被打乱。大货不像散单&#xff0c;试错成本高&#xff0c;下单前多花半小时核验供应商…

作者头像 李华
网站建设 2026/7/18 20:39:36

思源宋体CN:零成本提升中文设计档次的7种字重解决方案

思源宋体CN&#xff1a;零成本提升中文设计档次的7种字重解决方案 【免费下载链接】source-han-serif-ttf Source Han Serif TTF 项目地址: https://gitcode.com/gh_mirrors/so/source-han-serif-ttf 还在为中文设计项目找不到合适的免费商用字体而头疼吗&#xff1f;思…

作者头像 李华
网站建设 2026/7/18 20:31:50

跨云、跨地域数据同步,这样更省心

业务上云之后&#xff0c;数据分布往往变得很散。核心库可能在阿里云&#xff0c;分析平台可能在 AWS&#xff0c;一部分系统还在本地 IDC&#xff0c;海外业务又部署在新加坡。 这时候&#xff0c;跨云、跨地域数据同步就成了绕不开的问题。 如果源端和目标端在同一个内网环…

作者头像 李华
网站建设 2026/7/18 20:31:42

运维开发转网安居然不用从头学?吃透这 3 类岗位,转型事半功倍

收藏&#xff01;开发运维转网络安全不用重头学&#xff0c;3类宝藏岗位3个落地技巧 文章介绍了开发、运维人员如何转行网络安全领域&#xff0c;无需抛弃现有技能。重点推荐三类低门槛高适配岗位&#xff1a;安全运维工程师&#xff08;适合运维背景&#xff09;、Web安全测试…

作者头像 李华