【Hot 100 刷题计划】 LeetCode 238. 除自身以外数组的乘积 | C++ 前后缀分解题解

LeetCode 238. 除自身以外数组的乘积 | C++ 前后缀分解与 O(1) 空间优化

📌 题目描述

题目级别:中等

给你一个整数数组 nums,返回数组 answer ,其中 answer[i] 等于 nums 中除了 nums[i] 之外其余各元素的乘积。

题目数据 保证 数组 nums 之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

请不要使用除法,且在 O(N)O(N)O(N) 时间复杂度内完成此题。

  • 示例 1:
    输入: nums = [1,2,3,4]
    输出: [24,12,8,6]

💡 解题思路:前后缀分解

题目严禁使用除法(否则直接求总乘积再除以当前元素即可,还要考虑 0 的特判,非常麻烦)。

既然不能用除法,我们可以换个视角:"除自身以外的乘积",是不是就等于 "它左边所有数字的乘积" ×\times× "它右边所有数字的乘积"

🚀 解法一:双数组记录前后缀 (思路直观,空间 O(N))

我们可以开辟两个数组:

  1. L 数组:L[i] 记录索引 i 左侧所有元素的乘积。
  2. R 数组:R[i] 记录索引 i 右侧所有元素的乘积。
    最后遍历一次,answer[i] = L[i] * R[i] 即可。
💻 C++ 代码实现 (标准规范版)
cpp 复制代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        
        // 使用 vector 代替变长数组 (VLA),符合 C++ 标准规范
        vector<int> L(n, 1);
        vector<int> R(n, 1);
        vector<int> res(n);

        // 1. 计算左侧所有元素的乘积
        // L[0] 默认为 1,因为索引 0 左侧没有元素
        for (int i = 1; i < n; i++) {
            L[i] = L[i - 1] * nums[i - 1];
        }

        // 2. 计算右侧所有元素的乘积
        // R[n-1] 默认为 1,因为最后一个元素右侧没有元素
        for (int i = n - 2; i >= 0; i--) {
            R[i] = R[i + 1] * nums[i + 1];
        }

        // 3. 计算最终结果:左侧乘积 * 右侧乘积
        for (int i = 0; i < n; i++) {
            res[i] = L[i] * R[i];
        }

        return res;
    }
};

🏆 解法二:巧妙利用结果数组 (空间 O(1) 面试终极解)

面试官的终极追问:"输出数组不计入空间复杂度,你能只用 O(1)O(1)O(1) 的额外空间做出来吗?"
核心优化思想:动态计算,就地利用!

既然题目允许我们返回一个结果数组 res ,我们完全可以把 res 当作解法一中的 L 数组来用!
第一遍从左到右 :我们直接把前缀乘积计算好,存进 res 里。此时 res[i] 里装的就是左侧元素的乘积。
第二遍从右到左 :我们不再需要额外的 R 数组了。我们只需要用一个单独的整型变量 R_{mult} 来实时记录右侧的累计乘积。边走边乘: res[i] = res[i] * R_{mult} ,然后更新 R_{mult} = R_{mult} * nums[i] 。这样,完美省去了两个 O(N)O(N)O(N) 数组的开销!

💻 进阶 C++ 代码实现
cpp 复制代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> res(n, 1);

        // 第一遍:从左到右,res[i] 暂时用来存储左侧所有元素的乘积
        int L_mult = 1;
        for (int i = 0; i < n; i++) {
            res[i] = L_mult;
            L_mult *= nums[i]; // 更新前缀乘积
        }

        // 第二遍:从右到左,用一个变量 R_mult 动态追踪右侧元素的乘积
        int R_mult = 1;
        for (int i = n - 1; i >= 0; i--) {
            // 当前位置的最终结果 = 已经存好的左侧乘积 (res[i]) * 刚刚算好的右侧乘积 (R_mult)
            res[i] *= R_mult;
            // 更新后缀乘积,供下一个左边的元素使用
            R_mult *= nums[i]; 
        }

        return res;
    }
};
相关推荐
麻瓜code5 分钟前
【LeetCode】相交链表:双指针法,一次遍历找到交点
算法·leetcode·链表
charlie11451419114 分钟前
浅谈C++的列表初始化std::initializer_list
开发语言·数据结构·c++·list·开源项目
励志不掉头发的内向程序员21 分钟前
【LibreCAD 2D架构】RS_Line创建之后发生了什么?从图形容器到屏幕渲染
开发语言·c++·qt·学习·系统架构
zander25828 分钟前
LeetCode 5. 最长回文子串
算法
hanlin031 小时前
刷题笔记:力扣第84题-柱状图中最大的矩形
笔记·算法·leetcode
青少儿编程课堂2 小时前
威佐夫博弈(双堆取子游戏)解析
c++·python·算法·bfs·信息学竞赛
辰烨chenye6 小时前
LeetCode Hot 100 题解 · 普通数组篇
算法·leetcode·职场和发展
hqyjzsb10 小时前
零 AI 项目经验,学 Python 转型 AI 的正确顺序是什么?
开发语言·人工智能·python·算法·职场和发展·数据挖掘·数据分析
辰烨chenye10 小时前
LeetCode Hot 100 题解 · 二分篇
java·算法·leetcode
微功夫信息技术10 小时前
分层多智能体强化学习驱动的非急救转运公平 - 效率统一调度系统研究与实践
人工智能·学习·算法·动态规划