LeetCode 题解:除自身以外数组的乘积

在刷 LeetCode 的过程中,"除自身以外数组的乘积" 是一道经典的数组类题目,这道题不仅考察对数组遍历的理解,还要求在时间和空间复杂度上做到最优。今天就来分享这道题的高效解法,核心思路是通过两次遍历、利用两个变量分别记录左右侧乘积,实现时间复杂度 O (n)、空间复杂度 O (1)的最优解。

题目描述

给你一个长度为 n 的整数数组 nums,返回一个数组 ans,其中 ans i 等于 nums 中除 nums i 之外其余所有元素的乘积。要求不能使用除法,且在 O (n) 时间复杂度内完成,额外空间复杂度尽可能优化(除了答案数组外,仅使用常数级空间)。

解题思路

核心思想是拆分乘积计算:将 "除当前元素外所有元素的乘积" 拆分为 "当前元素左侧所有元素的乘积 × 当前元素右侧所有元素的乘积",通过两次遍历分别计算左右侧乘积,最终合并结果。

具体步骤:

  1. 定义两个变量 left 和 right,分别表示当前元素左侧所有元素的乘积、右侧所有元素的乘积,初始值均为 1(因为单个元素的左侧 / 右侧无元素,乘积为 1)。
  2. 定义答案数组 ans,长度与输入数组 nums 一致。
  3. 左到右遍历 :遍历每个元素时,先将当前 left(即该元素左侧所有元素的乘积)存入 ans[i],再更新 left 为 left × nums[i](为下一个元素的左侧乘积做准备)。
  4. 右到左遍历 :遍历每个元素时,将 ans[i](左侧乘积)乘以当前 right(右侧乘积),得到最终的 "除自身外所有元素的乘积",再更新 right 为 right × nums[i](为前一个元素的右侧乘积做准备)。
  5. 遍历完成后,ans 数组即为最终结果。

复杂度分析

  • 时间复杂度:O (n),仅需两次遍历数组,n 为数组长度。
  • 空间复杂度:O (1),除了存储结果的 ans 数组外,仅使用了 left 和 right 两个常数级变量(题目通常允许忽略答案数组的空间消耗)。

Java 代码

java 复制代码
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        // 定义答案数组
        int[] ans = new int[n];
        
        // 第一步:左到右遍历,计算每个元素左侧所有元素的乘积
        int left = 1;
        for (int i = 0; i < n; i++) {
            // 先将左侧乘积存入ans[i]
            ans[i] = left;
            // 更新left,为下一个元素的左侧乘积做准备
            left *= nums[i];
        }
        
        // 第二步:右到左遍历,计算右侧乘积并与左侧乘积相乘
        int right = 1;
        for (int i = n - 1; i >= 0; i--) {
            // 左侧乘积 × 右侧乘积 = 最终结果
            ans[i] *= right;
            // 更新right,为前一个元素的右侧乘积做准备
            right *= nums[i];
        }
        
        return ans;
    }
}

代码解析

  1. 初始化阶段:

    • int n = nums.length; 获取数组长度,用于定义答案数组和遍历边界。
    • int[] ans = new int[n]; 初始化答案数组,存储最终结果。
  2. 左到右遍历:

    • left 初始为 1,因为第一个元素左侧无元素,乘积为 1。
    • 遍历到第 i 个元素时,先把 left(左侧乘积)赋值给 ans[i],再让 left 乘以当前元素 nums[i],这样遍历结束后,ans[i] 存储的是第 i 个元素左侧所有元素的乘积。
  3. 右到左遍历:

    • right 初始为 1,因为最后一个元素右侧无元素,乘积为 1。
    • 遍历到第 i 个元素时,将 ans[i](左侧乘积)乘以 right(右侧乘积),得到该位置的最终结果;再让 right 乘以当前元素 nums[i],为前一个元素的右侧乘积做准备。

示例验证

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

  • 左到右遍历后,ans = [1, 1, 2, 6](分别对应每个元素左侧乘积:1、1、1×2、1×2×3)。
  • 右到左遍历:
    • i=3:ans[3] = 6 × 1 = 6,right = 1×4=4;
    • i=2:ans[2] = 2 × 4 = 8,right = 4×3=12;
    • i=1:ans[1] = 1 × 12 = 12,right = 12×2=24;
    • i=0:ans[0] = 1 × 24 = 24,right = 24×1=24;
  • 最终 ans = [24,12,8,6],符合 "除自身外所有元素乘积" 的结果。

总结

这道题的核心是拆分乘积计算,通过两次遍历分别处理左右侧乘积,避免了使用除法和额外的数组存储左右乘积,是空间优化的经典思路。关键点总结:

  1. 利用两个变量 left 和 right 分别记录当前元素的左右侧乘积,替代额外数组,实现空间复杂度 O (1);
  2. 两次遍历的顺序(先左后右)保证了每个位置的乘积能被逐步计算;
  3. 遍历过程中 "先赋值后更新" 的逻辑,确保了当前元素不会被包含到自身的左右乘积中。

这种思路不仅适用于这道题,也可以迁移到其他需要 "左右侧遍历" 的数组类题目中,希望能帮助大家理解和掌握~

相关推荐
云淡风轻~窗明几净10 分钟前
角谷猜想最接近严格证明的测试 Version1.1.1
人工智能·算法·机器学习·图论
机器学习之心24 分钟前
PINN × SVM × NSGA-II × 熵权TOPSIS:一套跑通「建模—优化—决策」的多目标优化框架
算法·机器学习·支持向量机
别动我齐刘海1 小时前
机器人 C++ / 系统联调补强2——异常处理、Protobuf
linux·c++·人工智能·学习·算法·机器学习·机器人
文丑颜不良啊2 小时前
LeetCode276~300题目解析
java·leetcode
荆棘鸟智能2 小时前
短历时洪水预报算法实践:水文模型与 LightGBM 残差订正融合
人工智能·算法·机器人
葫三生2 小时前
关于三生原理系列被技术开发社区批量收录或引发策展边界讨论的一种思考
大数据·人工智能·深度学习·算法·机器学习·数据挖掘·开源
catcatuncle2 小时前
OpenWorkBuddy 部署排错手册:从源码启动到自己搭一个给团队用,七个最容易卡住的地方
算法
avi91112 小时前
【AI教人】在Inspector显示所有Delegate-Odin Inspector之三
java·前端·算法·delegate·inspector·editor扩展·unity editor
淡海水3 小时前
06-02-源码-增量标记算法实现
算法·unity·c#·gc
碧海银沙音频科技研究院3 小时前
实现炬芯ATS308X的usb cdc 串口IAP 固件升级功能实现
人工智能·嵌入式硬件·深度学习·算法