从轮转数组到前后缀积:数组题开始不只是“模拟”,而是真正考思维了
2026/5/16 11:07:41 网站建设 项目流程

📌前言:

上一篇我们刚刷完轮转数组

那道题其实挺典型:

一开始最容易想到暴力移动,
后面逐渐发现可以通过反转数组完成优化。

说白了,轮转数组更偏向于:

“如何优化操作过程”

但当我刷到238. 除自身以外数组的乘积时,明显感觉难度又往上走了一层。

因为这次不再只是模拟过程,

而是开始真正要求你:

拆结构、找规律、避免重复计算。

刚看到题目时我第一反应还是:

“总乘积算出来,然后除自己不就行了?”

结果熟悉的限制来了:

  • 禁止除法
  • 时间复杂度 O(n)

只能说 LeetCode 太懂大家会偷懒

但看到题目要求:

  • 不能用除法
  • 时间复杂度 O(n)

当时我就知道,这题肯定没表面那么简单。

这道题特别像 LeetCode 里那种:

看着简单,实际很考察思维转换。

也是我第一次真正感受到:

“前缀思想”到底有多重要。


🟢 题目速览

LeetCode 238. 除自身以外数组的乘积

给你一个数组nums,返回一个新数组answer

answer[i] = nums中除了nums[i]之外所有元素的乘积

示例:

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

🧠 我的第一反应(大部分人都会这样想)


方法一:总乘积 / 当前值

比如:

总乘积 = 1×2×3×4 = 24

那么:

  • 24 / 1 = 24
  • 24 / 2 = 12
  • 24 / 3 = 8
  • 24 / 4 = 6

看起来完美。


❌ 但问题来了:

题目明确禁止除法。

而且如果数组里有 0:

[1,2,0,4]

直接炸掉。


所以这条路行不通。


🚀 真正解法:前缀积 + 后缀积


刚开始我其实没太想明白,后来画图后突然通了。


比如对于位置 i:

我们其实只需要:

左边所有数的乘积 × 右边所有数的乘积


举例:

nums = [1,2,3,4]

对于数字 3(下标2):

结果 = 1 × 2 × 4 = 8

也就是:

左边乘积 × 右边乘积

✅ 思路一下就清晰了:


第一步:

提前算好每个位置左边所有元素乘积

第二步:

再反向遍历,乘上右边所有元素乘积


✨ 解法代码(最好理解版本)

class Solution { public int[] productExceptSelf(int[] nums) { int n = nums.length; int[] left = new int[n]; int[] right = new int[n]; int[] ans = new int[n]; // left[i] 表示 i 左边所有元素乘积 left[0] = 1; for (int i = 1; i < n; i++) { left[i] = left[i - 1] * nums[i - 1]; } // right[i] 表示 i 右边所有元素乘积 right[n - 1] = 1; for (int i = n - 2; i >= 0; i--) { right[i] = right[i + 1] * nums[i + 1]; } // 最终结果 for (int i = 0; i < n; i++) { ans[i] = left[i] * right[i]; } return ans; } }

🔍 执行过程拆解


nums:

[1,2,3,4]

left数组:

[1,1,2,6]

解释:

  • left[0] = 1
  • left[1] = 1
  • left[2] = 1×2
  • left[3] = 1×2×3

right数组:

[24,12,4,1]

最终:

ans[i] = left[i] × right[i]

结果:

[24,12,8,6]

⚡ 更进一步:空间优化(面试更喜欢)

后来发现:

其实right数组根本没必要存。

用一个变量就行。

class Solution { public int[] productExceptSelf(int[] nums) { int n = nums.length; int[] ans = new int[n]; // 先存左边乘积 ans[0] = 1; for (int i = 1; i < n; i++) { ans[i] = ans[i - 1] * nums[i - 1]; } // 再动态乘右边乘积 int right = 1; for (int i = n - 1; i >= 0; i--) { ans[i] *= right; right *= nums[i]; } return ans; } }

🎯 这题我学到的东西

刷完之后我发现,

这题真正难的不是代码,

而是:

“如何避免重复计算”


你不是每次都重新算一遍,

而是提前把信息存起来。

这其实就是很多高频算法题的核心思想:

空间换时间。


⚠️ 易错点总结


❌ 不要忘记初始化:

ans[0] = 1; right = 1;

❌ 不要想着偷懒用除法

面试官基本就看你有没有理解前缀思想。


🧩 一句话总结


这题核心:

每个位置的答案 = 左边贡献 × 右边贡献


🎤 面试收尾(直接背)

“这道题本质上是通过前缀积和后缀积拆分每个位置的乘积贡献,避免重复遍历,从而在 O(n) 时间内完成结果计算,并可进一步将空间复杂度优化到 O(1)。”

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询