【LeetCode 热题 100】238. 除自身以外数组的乘积——(解法一)前缀积与后缀积
Problem: 238. 除自身以外数组的乘积
题目:给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请不要使用除法,且在 O(n) 时间复杂度内完成此题。
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(N)
- 空间复杂度:O(N)
整体思路
这段代码旨在解决一个经典的数组问题:除自身以外数组的乘积 (Product of Array Except Self)。问题要求计算一个 ans
数组,其中 ans[i]
等于输入数组 nums
中除 nums[i]
之外所有元素的乘积。一个重要的约束是,不能使用除法运算。
该算法采用了一种非常巧妙的 前缀积与后缀积 相结合的策略。它将复杂的计算分解为两个更简单的、可预处理的步骤。
-
核心思想:分解问题
- 对于任意索引
i
,ans[i]
的值可以看作是两部分的乘积:- 前缀积 (Prefix Product):
nums[i]
左侧所有元素的乘积,即nums[0] * nums[1] * ... * nums[i-1]
。 - 后缀积 (Suffix Product):
nums[i]
右侧所有元素的乘积,即nums[i+1] * nums[i+2] * ... * nums[n-1]
。
- 前缀积 (Prefix Product):
- 将这两部分相乘,
ans[i] = (前缀积) * (后缀积)
,就得到了除nums[i]
以外所有元素的乘积。
- 对于任意索引
-
预处理:计算前缀积和后缀积数组
- 算法创建了两个辅助数组
preMulti
和sufMulti
来存储所有可能的前缀积和后缀积。 - 计算前缀积:通过一次从左到右的遍历,计算
preMulti
数组。preMulti[i]
被设计用来存储nums[0...i-1]
的乘积。状态转移关系是preMulti[i+1] = preMulti[i] * nums[i]
,其基础是preMulti[0] = 1
(空前缀的乘积为1)。 - 计算后缀积:通过一次从右到左的遍历,计算
sufMulti
数组。sufMulti[i]
被设计用来存储nums[i+1...n-1]
的乘积。状态转移关系是sufMulti[j-1] = sufMulti[j] * nums[j]
,其基础是sufMulti[n] = 1
(空后缀的乘积为1)。
- 算法创建了两个辅助数组
-
合成最终结果
- 在两个辅助数组都填充好之后,最后通过一次遍历来构建
ans
数组。 - 对于每个索引
i
,直接从预处理好的数组中取出相应的前缀积和后缀积相乘,即ans[i] = preMulti[i] * sufMulti[i]
。
- 在两个辅助数组都填充好之后,最后通过一次遍历来构建
通过这种“空间换时间”的策略,算法避免了在计算每个 ans[i]
时都进行重复的乘法运算,从而实现了线性时间的解决方案。
完整代码
class Solution {/*** 计算一个数组,其中 ans[i] 等于 nums 中除 nums[i] 之外所有元素的乘积。* @param nums 输入的整数数组* @return 结果数组*/public int[] productExceptSelf(int[] nums) {int n = nums.length;// ans: 最终的结果数组int[] ans = new int[n];// preMulti: 存储前缀积。preMulti[i] 表示 nums[0]...nums[i-1] 的乘积。int[] preMulti = new int[n + 1];// sufMulti: 存储后缀积。sufMulti[i] 表示 nums[i+1]...nums[n-1] 的乘积。int[] sufMulti = new int[n + 1];// 初始化两个辅助数组的所有元素为 1。// 1 是乘法单位元,作为计算的起点(空前缀/后缀的积为1)。Arrays.fill(preMulti, 1);Arrays.fill(sufMulti, 1);// 步骤 1: 从左到右计算前缀积for (int i = 0; i < n; i++) {// preMulti[i+1] 是 preMulti[i] (nums[0...i-1]的积) 乘以当前元素 nums[i]preMulti[i + 1] = preMulti[i] * nums[i];}// 步骤 2: 从右到左计算后缀积// 注意循环从 n-1 开始,并且更新的是 j-1 的位置for (int j = n - 1; j > 0; j--) {// sufMulti[j-1] 是 sufMulti[j] (nums[j+1...n-1]的积) 乘以当前元素 nums[j]sufMulti[j - 1] = sufMulti[j] * nums[j];}// 步骤 3: 合成最终结果// 对于每个索引 i,其结果是其左侧所有元素的乘积与右侧所有元素的乘积的组合for (int i = 0; i < n; i++) {// ans[i] = (nums[0...i-1]的积) * (nums[i+1...n-1]的积)// 这恰好等于 preMulti[i] * sufMulti[i]ans[i] = preMulti[i] * sufMulti[i];}return ans;}
}
时空复杂度
时间复杂度:O(N)
- 初始化:
Arrays.fill
对两个长度为N+1
的数组进行填充,时间复杂度为 O(N) + O(N) = O(N)。 - 前缀积计算:第一个
for
循环遍历nums
数组一次,执行N
次操作。时间复杂度为 O(N)。 - 后缀积计算:第二个
for
循环从n-1
到1
,执行N-1
次操作。时间复杂度为 O(N)。 - 结果合成:第三个
for
循环遍历n
次以填充ans
数组。时间复杂度为 O(N)。
综合分析:
算法的总时间复杂度由几个独立的线性扫描组成:O(N) + O(N) + O(N) + O(N)。因此,最终的时间复杂度是 O(N)。
空间复杂度:O(N)
- 主要存储开销:算法创建了三个与输入规模相关的数组:
ans
,preMulti
, 和sufMulti
。int[] ans = new int[n]
: 占用了 O(N) 的空间。在一些语境下,输出结果不计入额外空间,但这里我们分析总占用。int[] preMulti = new int[n + 1]
: 占用了 O(N) 的额外辅助空间。int[] sufMulti = new int[n + 1]
: 占用了 O(N) 的额外辅助空间。
综合分析:
算法所需的额外辅助空间主要由 preMulti
和 sufMulti
两个数组决定。因此,其空间复杂度为 O(N) + O(N) = O(N)。
【LeetCode 热题 100】238. 除自身以外数组的乘积——(解法二)前缀积与后缀积+优化空间复杂度