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)}")

相关题目推荐

相关推荐
To_OC7 小时前
LC 131 分割回文串:刚学回溯时,我连怎么切字符串都想不明白
javascript·算法·leetcode
旖-旎8 小时前
LeetCode 518:零钱兑换||(完全背包)—— 题解
c++·算法·leetcode·动态规划·背包问题
To_OC9 小时前
LC 42 接雨水:暴力超时卡半天?前后缀数组一用就通了
javascript·算法·leetcode
delishcomcn9 小时前
AI视觉识别+分切算法:电化铝缺陷检测与裁切一体化解锁
人工智能·算法
触底反弹10 小时前
深入理解大模型采样:Temperature、Top-K、Top-P 的原理与实战
人工智能·算法·面试
雪碧聊技术10 小时前
力扣 LCR 091. 粉刷房子 —— 动态规划入门详解
算法·动态规划
CV-Climber13 小时前
检索技术的实际应用
人工智能·算法
hhzz13 小时前
Tiger AI Platform平台中增加人脸识别功能
图像处理·人工智能·算法·计算机视觉·大模型
从零开始的代码生活_14 小时前
C++ 继承详解:访问控制、对象模型、菱形继承与设计取舍
开发语言·c++·后端·学习·算法