【Day 11】238.除自身以外数组的乘积 - 详解

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

题目:

给你一个整数数组 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. 初始化两个空数组 L R。对于给定索引 i,L[i] 代表的是 i 左侧所有数字的乘积R[i] 代表的是 i 右侧所有数字的乘积
  2. 需要用两个循环来填充 L 和 R 数组的值。
    对于数组 L,L[0] 应该是 1,因为第一个元素的左边没有元素
    对于其他元素:L[i] = L[i-1] * nums[i-1]

当前位置 i 的左边积,等于它前一个位置的左边积,再乘上前一个位置的数。

  1. 同理,对于数组 R,R[length-1] 应为 1。length 指的是输入数组的大小。
    其他元素:R[i] = R[i+1] * nums[i+1]

当前位置 i 的右边积,等于它后一个位置的右边积,再乘上后一个位置的数。

  1. 当 R 和 L 数组填充完成,只需要在输入数组上迭代,且索引 i 处的值为:L[i] * R[i]

缺点:需要 O(n) 额外空间存储 L 和 R。

优化:只使用一个结果数组 ans,且不存储R数组,空间 O(1)。

L[i] 必须从左往右计算,所以可以先一边遍历一边计算好,放到 ans 里。(前缀积)
R[i] 必须从右往左计算,但其实不需要数组存下来,只要用一个变量 R 滚动维护右边乘积即可。(后缀积)

  1. 第一次从左往右遍历算出所有 L[i] 并存到 ans 数组里
    此时 ans[i] = L[i]
  2. 再从右往左遍历用一个变量 R 维护右边的乘积,不断更新
    每次让 ans[i] *= R(即在已有的左边积上再乘上右边积)。
    然后更新 R *= nums[i](让 R 包含当前位置的数,为下一步准备)。

举例:

假设:输入数组 nums = [1, 2, 3, 4],

方法一(原始思路,需要 L 和 R 两个数组)

  1. 先算左边乘积 L:
  • L[0] = 1(因为 0 左边没有数)
  • L[1] = L[0] * nums[0] = 1 * 1 = 1
  • L[2] = L[1] * nums[1] = 1 * 2 = 2
  • L[3] = L[2] * nums[2] = 2 * 3 = 6
    → L = [1, 1, 2, 6]
  1. 再算右边乘积 R:
  • R[3] = 1(因为最后一个数右边没有数)
  • R[2] = R[3] * nums[3] = 1 * 4 = 4
  • R[1] = R[2] * nums[2] = 4 * 3 = 12
  • R[0] = R[1] * nums[1] = 12 * 2 = 24
    → R = [24, 12, 4, 1]
  1. 最终结果 ans:
  • ans[i] = L[i] * R[i]
    → ans = [24, 12, 8, 6]

方法二(优化后,只用一个数组 + 一个变量 R)

第一步:计算左边乘积 L,存到 ans

  • 初始化 ans[0] = 1
  • ans[1] = ans[0] * nums[0] = 1 * 1 = 1
  • ans[2] = ans[1] * nums[1] = 1 * 2 = 2
  • ans[3] = ans[2] * nums[2] = 2 * 3 = 6
    → 此时 ans = [1, 1, 2, 6](其实就是 L)

第二步:再从右往左乘上右边积

  • 初始化 R = 1
  • i=3:ans[3] = ans[3] * R = 6 * 1 = 6;更新 R = R * nums[3] = 1 * 4 = 4
  • i=2:ans[2] = ans[2] * R = 2 * 4 = 8;更新 R = 4 * 3 = 12
  • i=1:ans[1] = ans[1] * R = 1 * 12 = 12;更新 R = 12 * 2 = 24
  • i=0:ans[0] = ans[0] * R = 1 * 24 = 24;更新 R = 24 * 1 = 24(用不到了)
    → 最终 ans = [24, 12, 8, 6]

代码实现(Go):

详细注解:

优化前:

// package main
// import "fmt"
func productExceptSelf(nums []int) []int {
n := len(nums)
L := make([]int, n) // L[i]: i 左侧所有元素的乘积
R := make([]int, n) // R[i]: i 右侧所有元素的乘积
ans := make([]int, n)
// 计算左侧乘积 L
L[0] = 1
for i := 1; i < n; i++ {
L[i] = L[i-1] * nums[i-1]
}
// 计算右侧乘积 R
R[n-1] = 1
for i := n - 2; i >= 0; i-- {
R[i] = R[i+1] * nums[i+1]
}
// 组合答案:ans[i] = 左乘积 * 右乘积
for i := 0; i < n; i++ {
ans[i] = L[i] * R[i]
}
return ans
}
// func main() {
// fmt.Println(productExceptSelf([]int{1, 2, 3, 4})) // 输出[24 12 8 6]
// }

优化后:

// package main
// import "fmt"
func productExceptSelf(nums []int) []int {
n := len(nums)
ans := make([]int, n) // 结果数组(通常不计入额外空间)
// 1) 先写入“左边乘积”:ans[i] = nums[0..i-1] 的乘积
// 最左边 i=0 的左侧没有元素,乘法单位元为 1
ans[0] = 1
for i := 1; i < n; i++ {
// ans[i-1] 已经是 nums[0..i-2] 的乘积,
// 乘上 nums[i-1] 就得到 nums[0..i-1] 的乘积
ans[i] = ans[i-1] * nums[i-1]
}
// 2) 再从右往左,用滚动变量 R 记录“右边乘积”
// 初始时最右端右侧无元素,因此取 1
R := 1
for i := n - 1; i >= 0; i-- {
// 循环从 i = n-1(最后一个位置)开始
// 当前 ans[i](左边乘积)乘上右边乘积 R,即为最终答案
ans[i] = ans[i] * R
// 把当前元素并入右边乘积,供左边的下一个 i-1 使用
R *= nums[i]
}
return ans
}
// func main() {
// fmt.Println(productExceptSelf([]int{1, 2, 3, 4})) // 输出[24 12 8 6]
// }

无注释(优化后):

package main
import "fmt"
func productExceptSelf(nums []int) []int {
n := len(nums)
ans := make([]int, n)
ans[0] = 1
for i := 1; i < n; i++ {
ans[i] = ans[i-1] * nums[i-1]
}
R := 1
for i := n - 1; i >= 0; i-- {
ans[i] *= R
R *= nums[i]
}
return ans
}
func main() {
fmt.Println(productExceptSelf([]int{
1, 2, 3, 4
})) // 输出[24 12 8 6]
}

posted @ 2025-08-25 22:35  yfceshi  阅读(11)  评论(0)    收藏  举报