前缀积与后缀积 —— LeetCode 238. 除了自身以外数组的乘积

题目描述(题目链接):

给定一个长度为 n的整数数组 nums,要求返回一个数组 answer,其中 answeri 等于 nums 中除了 numsi 之外其余各元素的乘积。

严苛的限制条件:

  1. 禁止使用除法:不能先求出总乘积再除以当前元素。
  2. 时间复杂度O(n):不能使用嵌套循环暴力求解。
  3. 进阶空间要求 O(1):除了输出数组外,不能申请额外的与 n相关的空间。

面对这些限制,传统的暴力解法(双重循环)和除法解法(需处理 0 和精度问题)均失效。我们需要一种更巧妙的前缀与后缀思想。

核心算法思想:前缀积 × 后缀积

对于数组中的任意一个位置 i,我们要求的 answeri 实际上可以拆解为两部分:

即:当前位置左侧所有元素的乘积(前缀积) 乘以 当前位置右侧所有元素的乘积(后缀积)。

算法设计步骤

为了满足 O(1)的额外空间复杂度(不计算输出数组),我们可以复用输出数组 answer 来存储中间结果,并通过两次遍历完成计算:

  1. 第一次遍历(从左到右) :
    计算每个位置 i 的左侧乘积 ,并直接存入 answeri 中。此时 answeri 表示 numsi 左边所有元素的乘积。
    • answer0 = 1(第一个元素左边无元素,乘积为 1)。
    • answeri = answeri-1 * numsi-1。
  2. 第二次遍历(从右到左) :
    使用一个变量 R 动态维护当前位置右侧所有元素的乘积 (初始 R = 1)。在遍历过程中,将 R 乘入 answeri,然后更新 R。
    • answeri = answeri * R(此时 answeri 变为左侧乘积 × 右侧乘积)。
  • R = R * numsi(更新 R,为下一个左侧元素做准备)。

代码实现 (C++)

以下是符合题目进阶空间要求的最优 C++ 实现:

cpp 复制代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> answer(n);

        // 第一步:计算左侧乘积并存入 answer
        // answer[i] 表示 nums[i] 左侧所有元素的乘积
        answer[0] = 1; 
        for (int i = 1; i < n; i++) {
            answer[i] = answer[i - 1] * nums[i - 1];
        }

        // 第二步:计算右侧乘积并直接与左侧乘积相乘
        int R = 1; // R 动态维护右侧元素的乘积
        for (int i = n - 1; i >= 0; i--) {
            answer[i] = answer[i] * R; // 左侧乘积 * 右侧乘积
            R = R * nums[i];           // 更新 R,供下一个(左侧)元素使用
        }

        return answer;
    }
};

算法复杂度分析

时间复杂度: O(n)

我们进行了两次遍历,每次遍历处理 n 个元素,总操作数为 2n,渐进时间复杂度为线性阶 O(n)。

空间复杂度: O(1)

除了必须返回的 answer 数组外,我们只使用了常数个变量(R 和循环索引 i),没有开辟额外的数组空间。这完美满足了题目进阶的 O(1)空间复杂度要求。

示例推演

以 nums = 1, 2, 3, 4 为例:

第一次遍历(计算左侧积):

i=0: answer0 = 1

i=1: answer1 = 1 * 1 = 1

i=2: answer2 = 1 * 2 = 2

i=3: answer3 = 2 * 3 = 6

此时 answer = 1, 1, 2, 6

第二次遍历(从右向左乘入右侧积):

初始 R = 1

i=3: answer3 = 6 * 1 = 6;R = 1 * 4 = 4

i=2: answer2 = 2 * 4 = 8;R = 4 * 3 = 12

i=1: answer1 = 1 * 12 = 12;R = 12 * 2 = 24

i=0: answer0 = 1 * 24 = 24;R = 24 * 1 = 24

最终输出: 24, 12, 8, 6,与预期结果完全一致。

相关推荐
C^h4 小时前
c++ string
开发语言·c++
raindayinrain4 小时前
c++高性能--原子操作,无锁编程,操作顺序
开发语言·c++·性能优化
学逆向的5 小时前
C++ 合并命名空间
开发语言·c++·网络安全
Zhao-Jichao5 小时前
VS2026 打包 C++ 程序生成 EXE,借助 Installer Project 扩展工具
开发语言·c++
学逆向的5 小时前
C 局部静态变量
c语言·开发语言·网络安全
小徐不徐说6 小时前
C++ 接口与插件设计:从工厂注册到 Qt 动态插件
数据库·c++·qt·系统架构·程序设计
大都督会赢的6 小时前
C++第一节课
开发语言·c++
UIU1146 小时前
P1233 [ICPC 2001 Taejon R] 木棍加工
数据结构·c++·算法·动态规划
“AI国潮设计-小江”6 小时前
【AIGC实战】Python+Stable Diffusion打造“英歌舞财神”IP,解锁“马到成功”等爆款情绪营销文案设计
开发语言·人工智能·python·prompt·aigc
小王C语言6 小时前
Qt常用控件:容器类控件,标题分组框、标签页(浏览器标签)
开发语言·qt