LeetCode 238. 除自身以外数组的乘积(前缀后缀乘积)

0 阅读3分钟

image.png

一、核心算法思想

题目:给定数组,返回新数组answeranswer[i]等于数组除nums[i]之外其余全部元素的乘积。禁止使用除法,时间复杂度 O (n)

公式:answer[i] = i位置左侧所有元素乘积 × i位置右侧所有元素乘积

  1. 第一趟从左向右遍历,在 ans 数组上直接保存左侧前缀乘积ans[0]=1,第一个元素左边没有数字。
  2. 第二趟从右向左遍历,用一个变量right滚动维护右侧后缀乘积
  3. ans[i] = ans[i] * right,把左侧乘积和右侧乘积相乘,得到最终结果。
  4. 更新right,给左边下一个下标使用。

不开额外后缀数组,额外空间 O (1),输出数组不计入空间开销。

二、完整带注释代码

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

        // 第一轮:计算每个下标 左侧所有元素的乘积
        ans[0] = 1; // 第0位左边没有元素,左侧乘积为1
        for(int i = 1; i < n; i++){
            ans[i] = ans[i-1] * nums[i-1];
        }

        // 第二轮:从右往左滚动计算后缀乘积
        int right = 1; // 最右边元素右侧没有元素,后缀乘积初始为1
        for(int i = n-1; i >= 0; i--){
            ans[i] = ans[i] * right;   // 左侧乘积 * 右侧乘积,得到答案
            right = right * nums[i];   // 更新后缀乘积,供左边i-1使用
        }
        return ans;
    }
}

三、实例推演

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

  1. 左向遍历(求前缀乘积)

    inums[i]ans [i] 含义(左侧乘积)ans 数组
    01左边无元素[1,_,_,_]
    12左边 [1] →1[1,1,_,_]
    23左边 [1,2] →2[1,1,2,_]
    34左边 [1,2,3] →6[1,1,2,6]
  2. 右向遍历,right=1

    ians[i]right (右侧乘积)ans [i]= 左 * right更新 right
    3616*1=6right=1*4=4
    2242*4=8right=4*3=12
    11121*12=12right=12*2=24
    01241*24=24right=24*1=24

最终输出:[24,12,8,6]

四、语法 & 内置函数、边界坑

  1. ❌暴力嵌套循环 O (n²),本题数据规模(10^5),直接超时,不能用 List 做删除模拟。
  2. 顺序不能颠倒:必须先算ans[i],再更新right。如果先更新 right,会把nums[i]自身算进乘积,结果出错。
  3. 初始化:ans[0]=1right=1。没有元素的时候乘积是 1(乘法单位元),不能初始化为 0。
  4. 输出数组长度必须等于原数组长度 n,不要写成 n‑1,否则数组越界。
  5. 题目限制不能用除法,如果数组包含 0,除法方案直接失效,这也是本题考点。

本题没有使用特殊内置 API,全部是数组基础遍历。

五、个人感悟

  1. 遇到不能用除法的乘积问题,优先想到前缀 + 后缀乘积拆分
  2. 可以利用输出数组原地存储前缀,再用单个变量滚动后缀,节省内存,不需要开两个大数组。
  3. 滚动变量的顺序是高频踩坑点:先用旧值计算结果,再更新变量,很多双指针、后缀遍历题目都遵守这个规律。