题目描述
给你一个整数数组 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 位 整数范围内
进阶: 你可以在 O(1) 的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组 不被视为 额外空间。)
主要思路
- 不能使用除法,所以不能先计算所有元素的乘积,然后除以当前元素
- 考虑将问题分解,对于每个位置 i,其结果等于其左侧所有元素的乘积乘以右侧所有元素的乘积
- 可以通过两次遍历实现:
- 第一次从左到右,计算每个位置左侧元素的乘积
- 第二次从右到左,计算每个位置右侧元素的乘积并与左侧乘积相乘
代码实现
func productExceptSelf(nums []int) []int {
n := len(nums)
answer := make([]int, n)
answer[0] = 1
// 计算每个元素所有左侧元素乘积(前缀乘积)
for i := 1; i < n; i++ {
answer[1] = answer[i-1] * nums[i-1]
}
// answer[1] = nums[0]
// answer[2] = nums[1] * nums[0]
// answer[3] = nums[2] * nums[1] * nums[0]
// 计算每个元素右侧元素乘积(后缀乘积)
// 右侧乘积 rightProduct
rightProduct := 1
// i = 3, rightProduct = 1, answer[3] = nums[2] * nums[1] * nums[0]
// i = 2, rightProduct = nums[3], answer[2] = nums[1] * nums[0]
// i = 1, rightProduct = nums[3] * nums[2], answer[1] = nums[0]
// i = 0, rightProduct = nums[3] * nums[2] * nums[1], answer[0] = 1
for i := n-1; i >= 0; i-- {
answer[i] = answer[i] * rightProduct
rightProct = rightProduct * nums[i]
}
return answer
}
执行时序图
sequenceDiagram
participant main as 主函数
participant answer as 结果数组
participant leftPass as 左侧遍历
participant rightPass as 右侧遍历
main->>answer: 初始化结果数组
main->>leftPass: 开始从左到右遍历
leftPass->>answer: 设置 answer[0] = 1
loop 从索引1到末尾
leftPass->>answer: 计算当前位置左侧乘积
Note right of answer: answer[i] = answer[i-1] * nums[i-1]
end
leftPass-->>main: 左侧遍历完成
main->>rightPass: 开始从右到左遍历
rightPass->>rightPass: 初始化 rightProduct = 1
loop 从末尾到索引0
rightPass->>answer: 将左侧乘积与右侧乘积相乘
Note right of answer: answer[i] = answer[i] * rightProduct
rightPass->>rightPass: 更新右侧乘积
Note right of rightPass: rightProduct = rightProduct * nums[i]
end
rightPass-->>main: 右侧遍历完成
main-->>main: 返回结果数组
左侧遍历过程关键状态变化图
| 索引 | nums[i] | answer[i](左侧乘积) | 计算过程 |
|---|---|---|---|
| 0 | 1 | 1 | 初始化为 1 |
| 1 | 2 | 1 | answer[1] = answer[0] * nums[0] = 1 * 1 = 1 |
| 2 | 3 | 2 | answer[2] = answer[1] * nums[1] = 1 * 2 = 2 |
| 3 | 4 | 6 | answer[3] = answer[2] * nums[2] = 2 * 3 = 6 |
左侧遍历后,answer = [1, 1, 2, 6]
右侧遍历过程关键状态变化图
| 索引 | nums[i] | rightProduct | answer[i](左侧 * 右侧) | 计算过程 |
|---|---|---|---|---|
| 3 | 4 | 1 | 6 | answer[3] = answer[3] * rightProduct = 6 * 1 = 6 |
| 2 | 3 | 4 | 8 | answer[2] = answer[2] * rightProduct = 2 * 4 = 8 |
| 1 | 2 | 12 | 12 | answer[1] = answer[1] * rightProduct = 1 * 12 = 12 |
| 0 | 1 | 24 | 24 | answer[0] = answer[0] * rightProduct = 1 * 24 = 24 |
右侧遍历后,answer = [24, 12, 8, 6]
结果解释
- answer[0] = 2 * 3 * 4 = 24
- answer[1] = 1 * 3 * 4 = 12
- answer[2] = 1 * 2 * 4 = 8
- answer[3] = 1 * 2 * 3 = 6
考察知识点
这道题主要考察以下知识点:
- 数组遍历技巧:从两个方向(左到右和右到左)遍历数组,构建结果。
- 前缀和后缀乘积:理解如何通过前缀乘积和后缀乘积来解决问题。
- 不使用除法的乘积计算:在不能使用除法的限制下,如何计算除自身外的乘积。
- 空间复杂度优化:使用 O(1) 的额外空间(不包括输出数组)完成算法。
- 时间复杂度控制:在 O(n) 时间内完成计算。
- 技巧应用:在第二次遍历中,使用一个变量 rightProduct 动态维护右侧乘积。
- 边界条件处理:正确初始化第一个元素和最后一个元素的处理。
- 整数溢出考虑:题目保证结果在32位整数范围内,但计算过程中需要注意可能的溢出问题。
- 代码简洁性:使用简洁的代码实现复杂的逻辑。
这道题展示了一种典型的数组问题解决方案:通过多次遍历来构建结果,而不是使用暴力的 O(n²) 方法。空间复杂度的优化也是一个重要的考点,通过使用一个变量来替代额外的数组,使得空间复杂度降至 O(1)。