算法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;
}

核心思想总结

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

相关推荐
小羊没烦恼!5 天前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
倒头就睡的小比特5 天前
算法竞赛C++常用的STL
c++·算法
俊昭喜喜里5 天前
java中的继承和多态的区别
java
小羊没烦恼!5 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
譕痕5 天前
JSONObject与JSONArray封装数据格式区别
java·json
胡写代码5 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖5 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商
此时不提桶,更待何时5 天前
01-06-A-JVM排查实战详解
java·jvm
猎头南楼5 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
vipxieliang5 天前
ValidX 在 DDD 领域驱动设计中的实践
java·spring boot