LeetCode 238. 除自身以外数组的乘积 - 题解
问题概述
给定一个整数数组 nums,返回一个数组 answer,其中 answer[i] 等于 nums 中除 nums[i] 之外其余所有元素的乘积。要求不能使用除法,且时间复杂度为 O (n)。
核心思路:前缀与后缀乘积的结合
解决问题的关键是将「除自身以外的乘积」拆分为两部分:
-
左侧乘积:当前元素左边所有元素的乘积(不包含自身)
-
右侧乘积:当前元素右边所有元素的乘积(不包含自身)
最终结果 answer[i] = 左侧乘积 × 右侧乘积
为了高效计算这两部分,我们引入前缀乘积数组 和后缀乘积数组:
-
前缀乘积数组
prefix:存储「从数组起点到当前位置的累积乘积」 -
后缀乘积数组
suffix:存储「从当前位置到数组终点的累积乘积」
详细步骤解析(含实例)
示例数组
以 nums = [1, 2, 3, 4] 为例,逐步演示计算过程。
1. 计算前缀乘积数组(prefix)
定义 :prefix[i] 表示 nums[0] × nums[1] × ... × nums[i](包含当前元素)
java
prefix[0] = nums[0]; // 第一个元素的前缀乘积就是自身
for (int i = 1; i < nums.length; i++) {
prefix[i] = nums[i] * prefix[i - 1]; // 累积前一个位置的乘积
}
计算过程:
-
prefix[0] = nums[0] = 1 -
prefix[1] = nums[1] × prefix[0] = 2 × 1 = 2 -
prefix[2] = nums[2] × prefix[1] = 3 × 2 = 6 -
prefix[3] = nums[3] × prefix[2] = 4 × 6 = 24
结果 :prefix = [1, 2, 6, 24]
2. 计算后缀乘积数组(suffix)
定义 :suffix[i] 表示 nums[i] × nums[i+1] × ... × nums[n-1](包含当前元素)
java
suffix[nums.length - 1] = nums[nums.length - 1]; // 最后一个元素的后缀乘积就是自身
for (int i = nums.length - 2; i >= 0; i--) {
suffix[i] = nums[i] * suffix[i + 1]; // 累积后一个位置的乘积
}
计算过程:
-
suffix[3] = nums[3] = 4 -
suffix[2] = nums[2] × suffix[3] = 3 × 4 = 12 -
suffix[1] = nums[1] × suffix[2] = 2 × 12 = 24 -
suffix[0] = nums[0] × suffix[1] = 1 × 24 = 24
结果 :suffix = [24, 24, 12, 4]
3. 计算最终结果(answer)
核心逻辑:
-
对于第
i个元素,左侧乘积 =prefix[i-1](若i > 0) -
对于第
i个元素,右侧乘积 =suffix[i+1](若i < n-1) -
边界情况:
-
第一个元素(
i=0):左侧无元素,结果 = 右侧乘积(suffix[1]) -
最后一个元素(
i=n-1):右侧无元素,结果 = 左侧乘积(prefix[n-2])
-
java
for (int i = 0; i < nums.length; i++) {
if (i == 0) {
answer[i] = suffix[i + 1]; // 第一个元素无左侧
} else if (i == nums.length - 1) {
answer[i] = prefix[i - 1]; // 最后一个元素无右侧
} else {
answer[i] = prefix[i - 1] * suffix[i + 1]; // 中间元素 = 左 × 右
}
}
计算过程 (以 nums = [1,2,3,4] 为例):
-
i=0:answer[0] = suffix[1] = 24 -
i=1:answer[1] = prefix[0] × suffix[2] = 1 × 12 = 12 -
i=2:answer[2] = prefix[1] × suffix[3] = 2 × 4 = 8 -
i=3:answer[3] = prefix[2] = 6
结果 :answer = [24, 12, 8, 6]
nums: [1, 2, 3, 4]
prefix: [1, 2, 6, 24] → 从左到右累积
suffix: [24, 24, 12, 4] → 从右到左累积
answer[i] 计算:
i=0 → 右侧:suffix[1] → 24
i=1 → prefix[0] × suffix[2] → 1×12=12
i=2 → prefix[1] × suffix[3] → 2×4=8
i=3 → 左侧:prefix[2] → 6
复杂度分析
-
时间复杂度:O (n),仅需 3 次线性遍历(前缀、后缀、结果计算)。
-
空间复杂度:O (n),需要额外存储前缀和后缀两个数组(可优化至 O (1),但此解法更直观)。
总结
通过前缀乘积和后缀乘积的拆分,我们巧妙避开了除法运算,同时满足了时间复杂度要求。核心思想是将「全局问题」拆解为「左侧局部乘积」和「右侧局部乘积」的组合,这种分治思想在数组类问题中非常常见。