题目描述

✅ 238. 除了自身以外数组的乘积

image-20260928215329598

题意分析

为每个下标 i 求出除 nums[i] 之外所有元素的乘积。排除的是当前位置,其他位置即使与它的值相同,也仍然要参与相乘。

题目要求不使用除法,并在 $O(n)$ 时间内完成;进阶要求只用 $O(1)$ 额外空间,返回数组不计入额外空间。数组允许包含零和负数,因此应直接组织需要相乘的元素,而不是先算总乘积再除去自身。

解法:前缀积 + 后缀积

核心思路

[!blue]

对位置 i 来说,除自身以外的元素恰好分成两段:[0, i - 1] 和 [i + 1, n - 1]。因此答案等于「左侧全部元素的乘积」乘以「右侧全部元素的乘积」,两段没有遗漏,也不会包含自身。

相邻位置的左侧区间只相差一个元素,可以从左向右不断累乘,不必为每个位置重新计算。右侧区间同理,只需改为从右向左。为了满足常数额外空间要求,直接把左侧乘积写进返回数组 res,右侧乘积则用一个滚动变量保存。

第一趟从左向右:处理位置 i 之前,left 等于 [0, i - 1] 的乘积。先写入 res[i] = left,保存不含自身的左侧乘积;再执行 left *= nums[i],使它成为下一个位置所需的左侧乘积。初始位置左边没有元素,空区间乘积取 $1$,所以从 left = 1 开始。

第二趟从右向左:处理位置 i 之前,right 等于 [i + 1, n - 1] 的乘积,而 res[i] 已保存左侧乘积。先执行 res[i] *= right,当前答案就完整了;再执行 right *= nums[i],为下一个向左访问的位置准备右侧乘积。最右侧的右边同样为空,因此初始化 right = 1。

两趟都遵循「先使用、后更新」,这正是排除自身的关键。遍历结束后,每个位置都已乘上完整的左右两段,返回 res 即可。零和负数会自然参与这些乘法:只有一个零时,非零位置的答案都包含该零;有多个零时,每个答案都包含零,无需另设分支。

解题步骤

  • 创建答案数组,初始化 left = 1。
  • 从左向右遍历:先令 res[i] = left,再执行 left *= nums[i]。
  • 初始化 right = 1,从右向左遍历:先执行 res[i] *= right,再执行 right *= nums[i]。
  • 返回答案数组。

代码实现

class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        int left = 1;

        for (int i = 0; i < n; i++) {
            // 先写不含当前位置的左侧乘积,再把当前值计入累计量。
            res[i] = left;
            left *= nums[i];
        }

        int right = 1;

        for (int i = n - 1; i >= 0; i--) {
            // 右侧也先使用后更新,保证当前位置不被乘进自己的答案。
            res[i] *= right;
            right *= nums[i];
        }

        return res;
    }
}
func productExceptSelf(nums []int) []int {
    n := len(nums)
    res := make([]int, n)
    left := 1
    for i := 0; i < n; i++ {
        // 先写不含当前位置的左侧乘积,再把当前值计入累计量。
        res[i] = left
        left *= nums[i]
    }

    right := 1
    for i := n - 1; i >= 0; i-- {
        // 右侧也先使用后更新,保证当前位置不被乘进自己的答案。
        res[i] *= right
        right *= nums[i]
    }
    return res
}

复杂度分析

  • 时间复杂度:$O(n)$,进行两趟线性遍历。
  • 空间复杂度:$O(1)$,按题意不计返回数组,额外只使用常数个变量;返回数组本身占 $O(n)$ 空间。

关键点总结

[!green]

  • 将“除自身外”拆成互不重叠的左右两段,分别计算后相乘。
  • 每次使用累计值时,它只包含已经遍历过的元素;当前元素要在本轮答案使用完后再乘入。
  • 返回数组暂存左侧乘积,反向遍历补入右侧乘积,不需要另外建立两个辅助数组。

易错点总结

[!yellow]

  • 用总乘积除以当前值:题目禁止除法,且遇到零会失效。
  • 第一趟先执行 left *= nums[i]:会把自身乘入左侧乘积。
  • 第二趟先更新 right:同样会把自身重复乘入答案。
  • 将 left 或 right 初始化为 $0$:空区间乘积应为乘法单位元 $1$,否则结果会全部归零。
  • 第二趟仍从左向右:无法在访问位置 $i$ 时得到其右侧区间的乘积。

相似题目

题目 难度 关联与区别
724. 寻找数组的中心下标 简单 同样把当前位置两侧拆成前缀与后缀,本题乘积排除自身,原题比较左右和。
42. 接雨水 困难 同样结合左右预处理信息,本题两侧相乘,原题由左右最高边界共同限制水位。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/75606665
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!