LeetCode 238 · 除自身以外数组的乘积:左右两遍扫描,不用除法

这道题的约束很"刁钻":O(n) 时间、不用除法、最好 O(1) 额外空间。三个条件同时满足,直接把"先算总乘积再除"和"前缀积数组"的方案都堵死了。但换个角度想------answeri 就是"左边的乘积 × 右边的乘积",分两遍扫就行了。


题目长什么样

给你一个整数数组 nums,返回数组 answer,其中 answer[i] 等于 nums 中除了 nums[i] 之外其余各元素的乘积。不要使用除法,O(n) 时间复杂度。

输入nums = [1,2,3,4]

输出[24,12,8,6]

说人话就是:对于每个位置,算其他所有位置的乘积。


第一反应:先算总乘积再除

text 复制代码
总乘积 = 1 × 2 × 3 × 4 = 24
answer[0] = 24 / 1 = 24
answer[1] = 24 / 2 = 12
answer[2] = 24 / 3 = 8
answer[3] = 24 / 4 = 6

但题目明确说"不要使用除法"------因为有 0 的情况。如果数组中有 0,总乘积就是 0,除法全部失效。所以这个方案直接淘汰。


第二反应:前缀积 + 后缀积

answer[i] 可以拆成两部分:

text 复制代码
answer[i] = nums[0] × ... × nums[i-1]  ×  nums[i+1] × ... × nums[n-1]
             ↑ 左边的乘积 (prefix)         ↑ 右边的乘积 (suffix)

如果能预先算出每个位置的"左乘积"和"右乘积",一乘就得到答案。

直观版本:两个额外数组

python 复制代码
class SolutionExtra:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        prefix = [1] * n
        suffix = [1] * n

        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]

        return [prefix[i] * suffix[i] for i in range(n)]

跑一遍示例 1:

text 复制代码
nums = [1, 2, 3, 4]

prefix: [1, 1, 2, 6]    ← prefix[i] = nums[0..i-1] 的乘积
suffix: [24, 12, 4, 1]  ← suffix[i] = nums[i+1..n-1] 的乘积

answer: [1×24, 1×12, 2×4, 6×1] = [24, 12, 8, 6] ✓
维度 说明
时间 O(n) 三次遍历
空间 O(n) prefix + suffix 两个数组

时间满足要求了,但空间是 O(n)。进阶要求 O(1) 额外空间------能做到吗?


最优解:用 answer 数组本身当前缀积

关键观察:输出数组不算额外空间 。所以我们可以直接用 answer 数组存前缀积,然后用一个变量从右往左累乘后缀积。

python 复制代码
class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        answer = [1] * n

        # 第一遍:从左到右,answer[i] = 左边所有元素的乘积
        left = 1
        for i in range(n):
            answer[i] = left
            left *= nums[i]

        # 第二遍:从右到左,answer[i] *= 右边所有元素的乘积
        right = 1
        for i in range(n - 1, -1, -1):
            answer[i] *= right
            right *= nums[i]

        return answer

跑一遍示例 1

text 复制代码
nums = [1, 2, 3, 4]

第一遍(从左到右): answer[i] = 左乘积
  i=0: answer[0]=1,  left=1×1=1
  i=1: answer[1]=1,  left=1×2=2
  i=2: answer[2]=2,  left=2×3=6
  i=3: answer[3]=6,  left=6×4=24
  answer = [1, 1, 2, 6]

第二遍(从右到左): answer[i] *= 右乘积
  i=3: answer[3]=6×1=6,  right=1×4=4
  i=2: answer[2]=2×4=8,  right=4×3=12
  i=1: answer[1]=1×12=12, right=12×2=24
  i=0: answer[0]=1×24=24, right=24×1=24
  answer = [24, 12, 8, 6] ✓

跑一遍示例 2

text 复制代码
nums = [-1, 1, 0, -3, 3]

第一遍(从左到右):
  answer = [1, -1, -1, 0, 0]

第二遍(从右到左):
  i=4: answer[4]=0×1=0,    right=1×3=3
  i=3: answer[3]=0×3=0,    right=3×(-3)=-9
  i=2: answer[2]=-1×(-9)=9, right=-9×0=0
  i=1: answer[1]=-1×0=0,   right=0×1=0
  i=0: answer[0]=1×0=0,    right=0×(-1)=0
  answer = [0, 0, 9, 0, 0] ✓
维度 说明
时间 O(n) 两遍扫描
空间 O(1) 只用了 leftright 两个变量(answer 不算)

两种解法放在一起看

解法 时间 空间 思路
前缀积 + 后缀积数组 O(n) O(n) 最直观,两个辅助数组
answer 复用 + 单变量 O(n) O(1) 最优解,两遍扫描

两种解法的核心思路完全一样------都是"左边乘积 × 右边乘积"。区别只在于存前缀积的方式:用额外数组还是复用 answer 数组。


这道题教会我什么

"拆成两部分"是解决乘积问题的万能思路

不允许用除法,那就把乘积拆成"左边"和"右边"。这个思路不依赖除法,而且天然 O(n)。类似的思想在树的问题里也很常见:对于某个节点,结果 = 左子树的信息 × 右子树的信息。

输出数组不算额外空间

这个约定在很多题目里都有。它意味着你可以把输出数组当工作空间使用------先存中间结果,再逐步更新成最终答案。这道题就是先存前缀积,再乘上后缀积。

两遍扫描的套路

text 复制代码
第一遍从左到右:累积左边的信息
第二遍从右到左:累积右边的信息

这个"两遍扫描"的套路在很多题目里都能看到:接雨水(LeetCode 42)、每日温度(LeetCode 739)、字符串解码(LeetCode 394)。掌握了这个模式,一整类题都能搞定。


完整测试代码

python 复制代码
from typing import List


class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        answer = [1] * n

        left = 1
        for i in range(n):
            answer[i] = left
            left *= nums[i]

        right = 1
        for i in range(n - 1, -1, -1):
            answer[i] *= right
            right *= nums[i]

        return answer


if __name__ == "__main__":
    s = Solution()

    nums = [1, 2, 3, 4]
    print(f"输入: {nums}, 输出: {s.productExceptSelf(nums)}")

    nums = [-1, 1, 0, -3, 3]
    print(f"输入: {nums}, 输出: {s.productExceptSelf(nums)}")

    nums = [2, 3, 0, 0]
    print(f"输入: {nums}, 输出: {s.productExceptSelf(nums)}")

    nums = [1, 2]
    print(f"输入: {nums}, 输出: {s.productExceptSelf(nums)}")

    nums = [4, 3, 2, 1, 2]
    print(f"输入: {nums}, 输出: {s.productExceptSelf(nums)}")

相关题目推荐

相关推荐
方方洛11 分钟前
vllm教程-03-架构设计
算法·架构
玄昌盛不会编程14 分钟前
LeetCode——2091. 从数组中移除最大值和最小值
java·算法·leetcode
如何取名26 分钟前
结合实际业务进行LRU淘汰算法优化设计(数据库开发日志)
数据库·算法
aichitang202430 分钟前
快乐泛函每一天:希尔伯特空间中的最佳逼近与正交投影定理
人工智能·考研·算法·ai·面试·泛函分析
淬炼之火35 分钟前
笔记:Visually-Guided Policy Optimization for Multimodal Reasoning
人工智能·笔记·算法·机器学习·语言模型·自然语言处理
一只QAQ37 分钟前
c++项目
java·c++·算法
学习星球41 分钟前
空天地一体化网络(NTN)深度解析:从Starlink D2C到3GPP NTN,卫星直连手机是如何实现的?
网络·人工智能·算法·智能手机·php
科技林总1 小时前
提示词测评落地全流程
人工智能·算法
晴天的雨.9921 小时前
类和对象下(内部类,匿名对象,对象拷贝时的编译器优化)
开发语言·c++·算法
旖旎夜光1 小时前
LeetCode 238:除自身以外数组的乘积(前缀和) —— 题解
数据结构·c++·算法·leetcode·前缀和