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

相关题目推荐

相关推荐
BAGAE17 小时前
FEC-RS前向纠错编码理论及工程实施研究
c语言·c++·qt·算法·决策树·链表
兰令水17 小时前
leecodecode【状态机DP】【2026.6.9打卡-java版本】
java·开发语言·算法
8Qi817 小时前
LeetCode 5:最长回文子串(Longest Palindromic Substring)—— 题解
算法·leetcode·职场和发展·动态规划
j7~17 小时前
【算法】专题一:双指针之移动零,复写零,快乐数
数据结构·c++·算法·双指针·快乐数·移动零·复写零
lqqjuly17 小时前
KAN 网络深度解析
算法
阿里matlab建模师17 小时前
【机场停机位分配】matlab实现基于遗传算法的机场停机位分配优化研究
开发语言·算法·数学建模·matlab·全国大学生数学建模竞赛
小雨下雨的雨1 天前
井字棋AI机器人实现详解 - Minimax算法实战-鸿蒙PC Electron框架完成
前端·人工智能·算法·华为·electron·鸿蒙
xieliyu.1 天前
Java算法精讲:双指针(三)
java·开发语言·算法
一条小锦吕*1 天前
基于Spring Boot + 数据可视化 + 协同过滤算法的推荐系统设计与实现(源码+论文+部署全讲解)
spring boot·算法·信息可视化