【Day 11】238.除自身以外数组的乘积
文章目录
- 238.除自身以外数组的乘积
- 题目:
- 思路:
- 举例:
- 方法一(原始思路,需要 L 和 R 两个数组)
- 方法二(优化后,只用一个数组 + 一个变量 R)
- 第一步:计算左边乘积 L,存到 ans
- 第二步:再从右往左乘上右边积
- 代码实现(Go):
- 优化前:
- 优化后:
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 位 整数范围内
思路:
利用索引左侧所有数字的乘积和右侧所有数字的乘积(即前缀积与后缀积)相乘得到答案。
- 初始化两个空数组
L
和R
。对于给定索引 i,L[i]
代表的是i 左侧所有数字的乘积
,R[i]
代表的是i 右侧所有数字的乘积
。 - 需要用两个循环来填充 L 和 R 数组的值。
对于数组 L,L[0] 应该是 1,因为第一个元素的左边没有元素
。
对于其他元素:L[i] = L[i-1] * nums[i-1]
。
当前位置 i 的左边积,等于它前一个位置的左边积,再乘上前一个位置的数。
- 同理,对于数组 R,
R[length-1] 应为 1
。length 指的是输入数组的大小。
其他元素:R[i] = R[i+1] * nums[i+1]
。
当前位置 i 的右边积,等于它后一个位置的右边积,再乘上后一个位置的数。
- 当 R 和 L 数组填充完成,只需要在输入数组上迭代,且索引 i 处的值为:
L[i] * R[i]
。
缺点:需要 O(n) 额外空间存储 L 和 R。
优化:只使用一个结果数组 ans,且不存储R数组,空间 O(1)。
L[i] 必须从左往右计算,所以可以先一边遍历一边计算好,放到 ans 里。
(前缀积)
R[i] 必须从右往左计算,但其实不需要数组存下来,只要用一个变量 R 滚动维护右边乘积即可。
(后缀积)
- 第一次从左往右遍历:
算出所有 L[i] 并存到 ans 数组里
。
此时ans[i] = L[i]
- 再从右往左遍历:
用一个变量 R 维护右边的乘积,不断更新
。
每次让ans[i] *= R
(即在已有的左边积上再乘上右边积)。
然后更新 R *= nums[i]
(让 R 包含当前位置的数,为下一步准备)。
举例:
假设:输入数组 nums = [1, 2, 3, 4],
方法一(原始思路,需要 L 和 R 两个数组)
- 先算左边乘积 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]
- 再算右边乘积 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]
- 最终结果 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)// 计算左侧乘积 LL[0] = 1for i := 1; i < n; i++ {L[i] = L[i-1] * nums[i-1]}// 计算右侧乘积 RR[n-1] = 1for 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 的左侧没有元素,乘法单位元为 1ans[0] = 1for 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 记录“右边乘积”// 初始时最右端右侧无元素,因此取 1R := 1for 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 mainimport "fmt"func productExceptSelf(nums []int) []int {n := len(nums)ans := make([]int, n)ans[0] = 1for i := 1; i < n; i++ {ans[i] = ans[i-1] * nums[i-1]}R := 1for i := n - 1; i >= 0; i-- {ans[i] *= RR *= nums[i]}return ans
}func main() {fmt.Println(productExceptSelf([]int{1, 2, 3, 4})) // 输出[24 12 8 6]
}