算法29,前缀和,除自身以外的数组的乘积

要解决"除自身以外数组的乘积"问题,需在 O(n) 时间复杂度 ​ 内完成计算,且不使用除法(避免除数为0等边界问题)。核心思路是利用 前缀积 ​ 和 后缀积​ 的组合,将时间复杂度优化至线性。

步骤1:理解问题本质

给定整数数组 nums,需构造结果数组 answer,使得 answer[i]等于 numsnums[i]自身外所有元素的乘积

例如:

  • nums = [1,2,3,4],则 answer[0] = 2×3×4 = 24answer[1] = 1×3×4 = 12,依此类推。

步骤2:设计"前缀积 + 后缀积"策略

直接暴力枚举每个元素、再遍历其余元素相乘(时间复杂度 O(n²))会超时,因此需用空间换时间

  • 前缀积数组 ff[i]表示 nums0, i-1 ​ 区间的元素乘积(即 nums[i]左侧所有元素的乘积)。

  • 后缀积数组 gg[i]表示 numsi+1, n-1 ​ 区间的元素乘积(即 nums[i]右侧所有元素的乘积)。

最终 answer[i] = f[i] × g[i](左侧乘积 × 右侧乘积,恰好是"除自身外所有元素的乘积")。

步骤3:推导数组递推关系

(1)前缀积数组 f的构造
  • 初始化:f[0] = 1(区间 [0, -1]无元素,乘积视为1)。

  • 递推公式:f[i] = f[i-1] × nums[i-1]

    解释:f[i]是"前i个元素的乘积",需基于 f[i-1](前i-1个元素的乘积)再乘以第 i-1个元素(nums[i-1])。

(2)后缀积数组 g的构造
  • 初始化:g[n-1] = 1(区间 [n, n-1]无元素,乘积视为1)。

  • 递推公式:g[i] = g[i+1] × nums[i+1]

    解释:g[i]是"从i到末尾的元素乘积",需基于 g[i+1](从i+1到末尾的乘积)再乘以第 i+1个元素(nums[i+1])。

步骤4:代码实现(以Java为例)

通过"前缀积 → 后缀积 → 结果合并"三步完成计算:

复制代码
public int[] productExceptSelf(int[] nums) {
    int n = nums.length;
    int[] f = new int[n]; // 前缀积数组
    int[] g = new int[n]; // 后缀积数组
    int[] answer = new int[n];
    
    // 1. 构造前缀积数组f
    f[0] = 1;
    for (int i = 1; i < n; i++) {
        f[i] = f[i-1] * nums[i-1];
    }
    
    // 2. 构造后缀积数组g
    g[n-1] = 1;
    for (int i = n-2; i >= 0; i--) {
        g[i] = g[i+1] * nums[i+1];
    }
    
    // 3. 计算结果:前缀积 × 后缀积
    for (int i = 0; i < n; i++) {
        answer[i] = f[i] * g[i];
    }
    
    return answer;
}

步骤5:复杂度分析

  • 时间复杂度:O(n)。三次线性遍历(构造前缀积、构造后缀积、合并结果),每次遍历时间为 O(n),总时间为 O(3n) = O(n)。

  • 空间复杂度 :O(n)(前缀积数组 f和后缀积数组 g各占 O(n);若优化空间,可将后缀积复用结果数组,将空间复杂度降为 O(1),逻辑本质不变)。

进阶:空间复杂度优化至 O(1)(除结果数组外)

利用结果数组 answer先存前缀积 ,再用一个变量 suffix动态维护后缀积,直接在结果数组中计算最终值:

复制代码
public int[] productExceptSelf(int[] nums) {
    int n = nums.length;
    int[] answer = new int[n];
    
    // 1. 先将前缀积存入answer
    answer[0] = 1;
    for (int i = 1; i < n; i++) {
        answer[i] = answer[i-1] * nums[i-1];
    }
    
    // 2. 用变量suffix存后缀积,从后往前更新answer
    int suffix = 1;
    for (int i = n-1; i >= 0; i--) {
        answer[i] = answer[i] * suffix; // 前缀积 × 后缀积
        suffix *= nums[i];             // 更新后缀积(下一个左元素需乘当前nums[i])
    }
    
    return answer;
}

核心思想总结

通过前缀积 覆盖"目标元素左侧所有元素的乘积",后缀积覆盖"目标元素右侧所有元素的乘积",两者相乘即为结果。这种方法避免了暴力枚举的高时间复杂度,同时利用数组的线性遍历特性,确保了效率与可读性。

相关推荐
智购科技自动售货机厂家1 小时前
2026自动售货机设备清洁效果自动验证:从图像比对到评分算法的工程实践~YH
人工智能·算法·计算机视觉
小刘在重生~1 小时前
Java常用类|String类详解 + BigDecimal精准计算(含面试题)
java·开发语言·python
随遇而安zx1 小时前
【多线程】---JMM 与 volatile 深度解析
java·多线程
姜穆澜1 小时前
机器学习实战指南:从算法原理到工程落地
人工智能·算法·机器学习
邪修king2 小时前
Re:Linux系统篇(十九):进程篇(八): 进程等待详解:wait/waitpid,僵尸进程到底该如何回收
java·开发语言
啵啵啵鱼2 小时前
DS-顺序表
java·数据结构·笔记·后端·链表·obsidian
TinyMemory2 小时前
Java 面向对象核心入门(七):方法重载 Overload,同一个方法名能干不一样的活
java·面向对象·overload·方法重载
Q一件事2 小时前
RWEQ——保留与消去P的soil_loss联合推导
算法