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

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

步骤1:理解问题本质

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

例如:

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

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

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

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

  • 后缀积数组 g :g[i]表示 nums中 i+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;
}

核心思想总结

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

相关推荐
烂蜻蜓2 分钟前
Pandas入门教程(四):Series数据结构——创建、索引与常用方法
数据结构·pandas
Wang's Blog4 分钟前
Java 项目实战: 交友项目 - 阿里云 OSS 入门案例:从创建 Bucket 到上传拿到可访问 URL
java·阿里云·交友
裕晟资质规划1 小时前
三类涉密资质延续时限差异分析:军工60日与集成印制3个月的边界
java·服务器·前端·网络·安全
念念清晰1 小时前
000-OpenJDK17 源码构建:MacOS环境(M芯片)
java·后端
Wang's Blog1 小时前
Java 项目实战: 交友项目 - 百度人脸识别入门案例
java·百度·交友
erzong2312 小时前
Linux操作系统:线程概念与控制
linux·算法
wuminyu2 小时前
JMM学习总结:深入理解happens-before关系
java·linux·c语言·jvm·c++
电商API_180079052472 小时前
反向海淘多币种结算:汇率该在哪个时点被冻结
java·开发语言·数据库·跨境电商·1688 api
Nil2082 小时前
leetcode 5最长回文子串
算法·leetcode·职场和发展
xxwl5852 小时前
区域赛分类题解
c++·笔记·学习·算法