中等
提示
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 不要使用除法, 且在 O(n) 时间复杂度内完成此题。
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
提示:
2 <= nums.length <= 105-30 <= nums[i] <= 30- 输入 保证 数组
answer[i]在 32 位 整数范围内
📝 核心笔记:除自身以外数组的乘积 (左右乘积列表)
1. 核心思想 (一句话总结)
"左边乘积x 右边乘积"。
想知道除了自己以外所有人的乘积,等于 (自己左边所有人的乘积) x (自己右边所有人的乘积)。
💡 直观理解:
不要试图去"剔除"自己(不用除法)。
而是把自己当成分界线,分别计算左半边和右半边的累积结果,最后左右一拍即合。
2. 算法流程 (三步走)
- 算左侧 ( pre**):** 从左往右扫,
pre[i]存0到i-1的乘积。 - 算右侧 ( suf**):** 从右往左扫,
suf[i]存i+1到n-1的乘积。 - 合并 ( ans**):**
ans[i] = pre[i] * suf[i]。
🔍 代码回忆清单 (带注释版)
// 题目:LC 238. 除自身以外数组的乘积
class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
// 1. 计算左侧乘积 (前缀积)
int[] pre = new int[n];
pre[0] = 1; // 关键点:最左边元素的左侧没有数字,视为 1
for (int i = 1; i < n; i++) {
pre[i] = pre[i - 1] * nums[i - 1]; // 注意是乘 nums[i-1]
}
// 2. 计算右侧乘积 (后缀积)
int[] suf = new int[n];
suf[n - 1] = 1; // 关键点:最右边元素的右侧没有数字,视为 1
for (int i = n - 2; i >= 0; i--) {
suf[i] = suf[i + 1] * nums[i + 1]; // 注意是乘 nums[i+1]
}
// 3. 合并结果
int[] ans = new int[n];
for (int i = 0; i < n; i++) {
ans[i] = pre[i] * suf[i]; // 左积 * 右积
}
return ans;
}
}
⚡ 快速复习 CheckList (易错点)
-
\] **边界初始值?** `pre[0]` 和 `suf[n-1]` 必须初始化为 **1**(乘法的单位元)。
-
pre从1开始(因为 0 已知)。suf从n-2开始(因为 n-1 已知)。
-
\] **索引错位?** 计算 `pre[i]` 时乘的是 `nums[i-1]`(它的前一个数);计算 `suf[i]` 时乘的是 `nums[i+1]`(它的后一个数)。
你当前的代码使用了 O(N) 的额外空间(pre 和 suf 数组)。
面试官通常会追问:如何用 O(1) 额外空间(输出数组不计入)?
优化思路:
-
先把
ans数组当做pre数组用(先算左侧)。 -
不创建
suf数组,而是用一个变量R(right) 动态维护右侧乘积。 -
从右往左倒着扫,边算
R边更新ans。// 空间优化逻辑概览
ans[i] = 左侧积; // 先存好左边
int R = 1;
for (int i = n-1; i >= 0; i--) {
ans[i] = ans[i] * R; // 左边 * 右边
R *= nums[i]; // R 累乘当前值,为下一个做准备
}