LeetCode hot100:238 除自身以外数组的乘积:不用除法的巧妙解法
问题描述:
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在  32 位 整数范围内。
请 不要使用除法,且在 O(n) 时间复杂度内完成此题。( 出于对空间复杂度分析的目的,输出数组 不被视为 额外空间。)
示例 1:
输入: nums = [1,2,3,4] 
输出: [24,12,8,6]
解释:
对于位置0: 2 × 3 × 4 = 24 对于位置1: 1 × 3 × 4 = 12 对于位置2: 1 × 2 × 4 = 8 对于位置3: 1 × 2 × 3 = 6
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
解释:注意数组中存在0的情况
解决方法:
方法一:显式的前缀和后缀数组
算法思路:
- 分别构建前缀乘积数组和后缀乘积数组;
 前缀乘积数组:
prefix[i] = nums[0] × nums[1] × ... × nums[i-1];后缀乘积数组:
suffix[i] = nums[i+1] × nums[i+2] × ... × nums[n-1];最终结果:
result[i] = prefix[i] × suffix[i]
class Solution:def productExceptSelf(self, nums: List[int]) -> List[int]:n = len(nums)# 创建前缀乘积数组和后缀乘积数组prefix = [1] * n   # prefix[i] 表示 nums[0]到nums[i-1]的乘积suffix = [1] * n   # suffix[i] 表示 nums[i+1]到nums[n-1]的乘积# 计算前缀乘积数组for i in range(1, n):prefix[i] = prefix[i-1] * nums[i-1]# 计算后缀乘积数组for i in range(n-2, -1, -1):suffix[i] = suffix[i+1] * nums[i+1]# 合并前缀和后缀乘积result = [1] * nfor i in range(n):result[i] = prefix[i] * suffix[i]return result复杂度分析:
- 时间复杂度:O(n) 三次线性遍历
 - 空间复杂度:O(n) 需要额外的前缀和后缀数组
 
方法二:前缀乘积与后缀乘积(优)
class Solution:def productExceptSelf(self, nums: List[int]) -> List[int]:n = len(nums)  # 获取输入数组的长度# 初始化结果数组,每个位置先设为1,因为乘法中1是单位元result = [1] * n# 第一步:计算每个位置的前缀乘积(不包含当前位置元素)prefix = 1  # 初始化前缀乘积为1for i in range(n):result[i] = prefix  # 将当前位置的前缀乘积存入结果数组prefix *= nums[i]   # 更新前缀乘积,包含当前元素用于下一个位置的计算# 第二步:计算每个位置的后缀乘积,并与前缀乘积相乘得到最终结果suffix = 1  # 初始化后缀乘积为1for i in range(n-1, -1, -1):  # 从数组末尾向前遍历result[i] *= suffix  # 将当前位置的前缀乘积与后缀乘积相乘suffix *= nums[i]    # 更新后缀乘积,包含当前元素用于前一个位置的计算return result  # 返回最终的结果数组算法步骤:
第一次正向遍历:计算每个位置左侧所有元素的乘积(前缀乘积);
第二次反向遍历:计算每个位置右侧所有元素的乘积(后缀乘积),并与前缀乘积相乘。
复杂度分析:
- 时间复杂度:O(n) 两次线性遍历
 - 空间复杂度:O(1) 只使用常数空间(输出数组不计入)
 
问题详解:
1、为什么不使用除法:
使用除法很容易解决这个问题
# 简单但不符合要求的解法
def productExceptSelf(nums):total_product = 1for num in nums:total_product *= numresult = []for num in nums:result.append(total_product // num)return result存在的缺陷:
当数组中有0时会出现除零错误;
题目明确要求不能使用除法;
如果乘积超出整数范围会有问题。
2、处理特殊情况:包含0的情况
方法一和方法二都能正确处理0的情况;
不需要特殊处理,算法自然能够处理。
总结:
关键要点:核心思想:将问题分解为"左侧乘积"和"右侧乘积"两部分;空间优化:通过巧妙的遍历顺序,可以在常数空间内完成计算;边界处理:算法自然处理了数组边界情况,不需要特殊判断;零值处理:即使数组中有0,算法也能正确工作。
推荐第二种方法,是经典解法。
