按照改进方案,为以下6个二叉树题目增强了解题思路的详细程度: 1. 二叉树的中序遍历 - 增加"思路推导"部分,解释递归到迭代的转换 - 详细说明迭代法的每个步骤 - 增加执行过程演示和多种解法 2. 二叉树的最大深度 - 增加"思路推导",对比DFS和BFS - 详细解释递归的基准情况 - 增加多种解法和变体问题 3. 从前序与中序遍历序列构造二叉树 - 详细解释前序和中序的特点 - 增加"思路推导",说明如何分治 - 详细说明切片边界计算 4. 对称二叉树 - 解释镜像对称的定义 - 详细说明递归比较的逻辑 - 增加迭代解法和变体问题 5. 翻转二叉树 - 解释翻转的定义和过程 - 详细说明多值赋值的执行顺序 - 增加多种解法和有趣的故事 6. 路径总和 - 详细解释路径和叶子节点的定义 - 说明为什么使用递减而非累加 - 增加多种解法和变体问题 每个文件都包含: - 完整的示例和边界条件分析 - 详细的算法流程和图解 - 关键细节说明 - 常见错误分析 - 复杂度分析(详细版) - 执行过程演示 - 多种解法 - 变体问题 - 总结 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
690 lines
15 KiB
Markdown
690 lines
15 KiB
Markdown
# 除自身以外数组的乘积 (Product of Array Except Self)
|
||
|
||
LeetCode 238. Medium
|
||
|
||
## 题目描述
|
||
|
||
给你一个整数数组 `nums`,返回数组 `answer`,其中 `answer[i]` 等于 `nums` 中除 `nums[i]` 之外其余各元素的乘积。
|
||
|
||
**题目要求**:请不要使用除法,且在 O(n) 时间复杂度内完成此题。
|
||
|
||
**示例 1**:
|
||
```
|
||
输入: nums = [1,2,3,4]
|
||
输出: [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
|
||
```
|
||
|
||
**示例 2**:
|
||
```
|
||
输入: nums = [-1,1,0,-3,3]
|
||
输出: [0,0,9,0,0]
|
||
```
|
||
|
||
**进阶**:你可以在 O(1) 的额外空间复杂度(输出数组不被视为额外空间)内完成此题目吗?
|
||
|
||
## 思路推导
|
||
|
||
### 暴力解法分析
|
||
|
||
**最直观的思路**:对于每个位置,计算其他所有元素的乘积。
|
||
|
||
```python
|
||
def productExceptSelf(nums):
|
||
n = len(nums)
|
||
answer = []
|
||
|
||
for i in range(n):
|
||
product = 1
|
||
for j in range(n):
|
||
if j != i:
|
||
product *= nums[j]
|
||
answer.append(product)
|
||
|
||
return answer
|
||
```
|
||
|
||
**时间复杂度**:O(n²)
|
||
- 外层循环:O(n)
|
||
- 内层循环:O(n)
|
||
- 总计:O(n) × O(n) = O(n²)
|
||
|
||
**空间复杂度**:O(1),不考虑输出数组
|
||
|
||
**问题分析**:
|
||
1. 效率低:n=10⁵ 时,n² 不可接受
|
||
2. 重复计算:很多乘积被多次计算
|
||
3. 无法利用已知信息
|
||
|
||
### 优化思考 - 第一步:使用除法
|
||
|
||
**观察**:如果可以使用除法
|
||
|
||
```python
|
||
total_product = 1
|
||
for num in nums:
|
||
total_product *= num
|
||
|
||
answer[i] = total_product / nums[i]
|
||
```
|
||
|
||
**问题**:
|
||
1. 题目禁止使用除法
|
||
2. 如果 nums[i] = 0,除法会出错
|
||
3. 如果多个 0,需要特殊处理
|
||
|
||
### 优化思考 - 第二步:分离左右乘积
|
||
|
||
**关键观察**:`answer[i] = 左侧乘积 × 右侧乘积`
|
||
|
||
```
|
||
nums = [1, 2, 3, 4]
|
||
|
||
answer[0] = (空) × (2 × 3 × 4) = 左侧[0] × 右侧[0]
|
||
answer[1] = (1) × (3 × 4) = 左侧[1] × 右侧[1]
|
||
answer[2] = (1 × 2) × (4) = 左侧[2] × 右侧[2]
|
||
answer[3] = (1 × 2 × 3) × (空) = 左侧[3] × 右侧[3]
|
||
```
|
||
|
||
**为什么这样思考?**
|
||
- 每个位置的答案可以分解为两部分
|
||
- 左侧部分:i 之前所有元素的乘积
|
||
- 右侧部分:i 之后所有元素的乘积
|
||
- 两部分独立计算,然后相乘
|
||
|
||
**优化后的思路**:
|
||
```python
|
||
# 预计算左侧乘积
|
||
left = [1] * n
|
||
for i in range(1, n):
|
||
left[i] = left[i-1] * nums[i-1]
|
||
|
||
# 预计算右侧乘积
|
||
right = [1] * n
|
||
for i in range(n-2, -1, -1):
|
||
right[i] = right[i+1] * nums[i+1]
|
||
|
||
# 合并结果
|
||
answer = [left[i] * right[i] for i in range(n)]
|
||
```
|
||
|
||
**时间复杂度**:O(n)
|
||
- 计算左侧:O(n)
|
||
- 计算右侧:O(n)
|
||
- 合并结果:O(n)
|
||
- 总计:O(n)
|
||
|
||
**空间复杂度**:O(n)
|
||
- 左侧数组:O(n)
|
||
- 右侧数组:O(n)
|
||
- 输出数组:O(n)
|
||
- 总计:O(n)
|
||
|
||
### 优化思考 - 第三步:空间优化
|
||
|
||
**问题**:题目要求 O(1) 额外空间(输出数组除外)
|
||
|
||
**关键优化**:用输出数组存储左侧乘积,用变量累积右侧乘积
|
||
|
||
```python
|
||
# 用 answer 存储左侧乘积
|
||
answer = [1] * n
|
||
for i in range(1, n):
|
||
answer[i] = answer[i-1] * nums[i-1]
|
||
|
||
# 用变量累积右侧乘积,直接更新 answer
|
||
right = 1
|
||
for i in range(n-1, -1, -1):
|
||
answer[i] *= right
|
||
right *= nums[i]
|
||
```
|
||
|
||
**为什么这样思考?**
|
||
- 输出数组不被视为额外空间
|
||
- 右侧乘积可以用一个变量累积
|
||
- 从右向左遍历时,边计算边更新
|
||
|
||
**空间复杂度**:O(1)
|
||
- 只用了一个变量 `right`
|
||
- 输出数组 `answer` 不计入
|
||
|
||
## 解题思路
|
||
|
||
### 核心思想
|
||
|
||
**分离左右乘积**:将 `answer[i]` 分解为左侧乘积和右侧乘积的乘积。
|
||
|
||
**为什么这样思考?**
|
||
|
||
1. **分解问题的思想**:
|
||
- 复杂问题 → 简单子问题
|
||
- 每个位置的答案 = 左边所有数的乘积 × 右边所有数的乘积
|
||
|
||
2. **独立计算的优势**:
|
||
- 左侧乘积可以从左到右递推计算
|
||
- 右侧乘积可以从右到左递推计算
|
||
- 两部分互不干扰
|
||
|
||
3. **空间优化的技巧**:
|
||
- 用输出数组存储左侧乘积
|
||
- 用变量累积右侧乘积
|
||
- 边计算边更新,避免额外数组
|
||
|
||
### 详细算法流程
|
||
|
||
**步骤1:计算左侧乘积(存储在 answer 中)**
|
||
|
||
```python
|
||
answer = [1] * n
|
||
|
||
# answer[i] = nums[0] × ... × nums[i-1]
|
||
for i in range(1, n):
|
||
answer[i] = answer[i-1] * nums[i-1]
|
||
```
|
||
|
||
**关键点**:
|
||
- `answer[0] = 1`(0 左侧没有元素)
|
||
- `answer[i]` 依赖于 `answer[i-1]`
|
||
- 递推关系:`answer[i] = answer[i-1] × nums[i-1]`
|
||
|
||
**示例**:
|
||
```
|
||
nums = [1, 2, 3, 4]
|
||
|
||
i=0: answer[0] = 1
|
||
i=1: answer[1] = answer[0] × nums[0] = 1 × 1 = 1
|
||
i=2: answer[2] = answer[1] × nums[1] = 1 × 2 = 2
|
||
i=3: answer[3] = answer[2] × nums[2] = 2 × 3 = 6
|
||
|
||
answer = [1, 1, 2, 6] (左侧乘积)
|
||
```
|
||
|
||
**步骤2:计算右侧乘积并更新答案**
|
||
|
||
```python
|
||
right = 1
|
||
|
||
for i in range(n-1, -1, -1):
|
||
# answer[i] *= 右侧乘积
|
||
answer[i] *= right
|
||
# 更新右侧乘积
|
||
right *= nums[i]
|
||
```
|
||
|
||
**关键点**:
|
||
- `right` 初始为 1(最右侧右侧没有元素)
|
||
- 从右向左遍历
|
||
- 递推关系:`right = right × nums[i]`
|
||
|
||
**示例(续)**:
|
||
```
|
||
nums = [1, 2, 3, 4]
|
||
answer = [1, 1, 2, 6] (左侧乘积)
|
||
|
||
i=3: answer[3] = 6 × 1 = 6, right = 1 × 4 = 4
|
||
i=2: answer[2] = 2 × 4 = 8, right = 4 × 3 = 12
|
||
i=1: answer[1] = 1 × 12 = 12, right = 12 × 2 = 24
|
||
i=0: answer[0] = 1 × 24 = 24, right = 24 × 1 = 24
|
||
|
||
answer = [24, 12, 8, 6] (最终结果)
|
||
```
|
||
|
||
### 关键细节说明
|
||
|
||
**细节1:为什么 answer[0] = 1?**
|
||
|
||
```python
|
||
answer = [1] * n # 所有位置初始化为 1
|
||
```
|
||
|
||
**原因**:
|
||
- `answer[0]` 表示 nums[0] 左侧所有元素的乘积
|
||
- nums[0] 左侧没有元素
|
||
- 空乘积定义为 1(乘法的单位元)
|
||
- 类似:`answer[n-1]` 右侧也没有元素,right 初始为 1
|
||
|
||
**细节2:为什么是 `answer[i] = answer[i-1] × nums[i-1]`?**
|
||
|
||
```python
|
||
# 错误写法
|
||
answer[i] = answer[i-1] * nums[i] # 错误!会包含当前元素
|
||
|
||
# 正确写法
|
||
answer[i] = answer[i-1] * nums[i-1] # 正确
|
||
```
|
||
|
||
**原因**:
|
||
- `answer[i]` 是 nums[i] 左侧的乘积
|
||
- 不应该包含 nums[i] 本身
|
||
- 示例:
|
||
```
|
||
nums = [1, 2, 3, 4]
|
||
answer[2] = nums[0] × nums[1] = 1 × 2 = 2
|
||
不包含 nums[2] = 3
|
||
```
|
||
|
||
**细节3:为什么从右向左遍历?**
|
||
|
||
```python
|
||
# 正确:从右向左
|
||
for i in range(n-1, -1, -1):
|
||
answer[i] *= right
|
||
right *= nums[i]
|
||
|
||
# 错误:从左向右
|
||
for i in range(n):
|
||
answer[i] *= right # right 值不对
|
||
right *= nums[i]
|
||
```
|
||
|
||
**原因**:
|
||
- `right` 是 nums[i] 右侧的乘积
|
||
- 必须先计算右侧的值
|
||
- 从右向左遍历才能保证 `right` 是正确的
|
||
|
||
**细节4:为什么顺序是先更新 answer,再更新 right?**
|
||
|
||
```python
|
||
# 正确顺序
|
||
answer[i] *= right # 先使用当前的 right
|
||
right *= nums[i] # 再更新 right
|
||
|
||
# 错误顺序
|
||
right *= nums[i] # 错误!先更新了
|
||
answer[i] *= right # right 包含了 nums[i] 本身
|
||
```
|
||
|
||
**原因**:
|
||
- `right` 应该是 nums[i] 右侧的乘积
|
||
- 不应该包含 nums[i] 本身
|
||
- 必须先使用当前的 `right`,再更新
|
||
|
||
### 边界条件分析
|
||
|
||
**边界1:数组长度为 1**
|
||
|
||
```
|
||
输入:nums = [5]
|
||
输出:[1]
|
||
解释:
|
||
answer[0] = 1(左侧为空)
|
||
right = 1(右侧为空)
|
||
answer[0] = 1 × 1 = 1
|
||
```
|
||
|
||
**边界2:包含 0**
|
||
|
||
```
|
||
输入:nums = [1, 0, 3, 4]
|
||
过程:
|
||
answer = [1, 1, 0, 0] (左侧乘积)
|
||
|
||
i=3: answer[3] = 0 × 1 = 0, right = 4
|
||
i=2: answer[2] = 0 × 4 = 0, right = 12
|
||
i=1: answer[1] = 1 × 12 = 12, right = 0
|
||
i=0: answer[0] = 1 × 0 = 0, right = 1
|
||
|
||
输出:[0, 12, 0, 0]
|
||
验证:
|
||
answer[0] = 0 × 3 × 4 = 0 ✓
|
||
answer[1] = 1 × 3 × 4 = 12 ✓
|
||
answer[2] = 1 × 0 × 4 = 0 ✓
|
||
answer[3] = 1 × 0 × 3 = 0 ✓
|
||
```
|
||
|
||
**边界3:包含多个 0**
|
||
|
||
```
|
||
输入:nums = [0, 1, 0, 3]
|
||
过程:
|
||
answer = [1, 0, 0, 0] (左侧乘积)
|
||
|
||
i=3: answer[3] = 0 × 1 = 0, right = 3
|
||
i=2: answer[2] = 0 × 3 = 0, right = 0
|
||
i=1: answer[1] = 0 × 0 = 0, right = 0
|
||
i=0: answer[0] = 1 × 0 = 0, right = 0
|
||
|
||
输出:[0, 0, 0, 0]
|
||
验证:
|
||
任意位置都会乘到至少一个 0
|
||
```
|
||
|
||
**边界4:负数**
|
||
|
||
```
|
||
输入:nums = [-1, -2, -3, -4]
|
||
过程:
|
||
answer = [1, -1, 2, -6] (左侧乘积)
|
||
|
||
i=3: answer[3] = -6 × 1 = -6, right = -4
|
||
i=2: answer[2] = 2 × (-4) = -8, right = 12
|
||
i=1: answer[1] = -1 × 12 = -12, right = -24
|
||
i=0: answer[0] = 1 × (-24) = -24, right = 24
|
||
|
||
输出:[-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(n),遍历一次
|
||
- 计算右侧乘积:O(n),遍历一次
|
||
- 总计:O(n) + O(n) = O(n)
|
||
|
||
为什么是 O(n)?
|
||
- 两次线性扫描
|
||
- 每个元素只访问一次
|
||
- 常数操作(乘法和赋值)
|
||
```
|
||
|
||
**空间复杂度**:
|
||
```
|
||
- 输出数组:O(n),不计入额外空间
|
||
- 右侧变量:O(1)
|
||
- 循环变量:O(1)
|
||
- 总计:O(1)(满足题目要求)
|
||
```
|
||
|
||
---
|
||
|
||
## 图解过程
|
||
|
||
```
|
||
nums = [1, 2, 3, 4]
|
||
|
||
步骤1:计算左侧乘积
|
||
answer[0] = 1
|
||
answer[1] = answer[0] × nums[0] = 1 × 1 = 1
|
||
answer[2] = answer[1] × nums[1] = 1 × 2 = 2
|
||
answer[3] = answer[2] × nums[2] = 2 × 3 = 6
|
||
|
||
answer = [1, 1, 2, 6]
|
||
|
||
图示:
|
||
nums: [1, 2, 3, 4]
|
||
↓
|
||
answer: [1, 1, 2, 6]
|
||
(×1)(×1)(×2)(×6) 左侧累积
|
||
|
||
步骤2:计算右侧乘积并更新
|
||
right = 1
|
||
|
||
i=3: answer[3] = 6 × 1 = 6, right = 1 × 4 = 4
|
||
i=2: answer[2] = 2 × 4 = 8, right = 4 × 3 = 12
|
||
i=1: answer[1] = 1 × 12 = 12, right = 12 × 2 = 24
|
||
i=0: answer[0] = 1 × 24 = 24, right = 24 × 1 = 24
|
||
|
||
answer = [24, 12, 8, 6]
|
||
|
||
图示:
|
||
nums: [1, 2, 3, 4]
|
||
↓
|
||
answer: [24, 12, 8, 6]
|
||
(×24)(×12)(×8)(×6) 右侧累积
|
||
|
||
最终结果:
|
||
answer[0] = 1 × (2 × 3 × 4) = 24
|
||
answer[1] = (1) × (3 × 4) = 12
|
||
answer[2] = (1 × 2) × (4) = 8
|
||
answer[3] = (1 × 2 × 3) × 1 = 6
|
||
```
|
||
|
||
---
|
||
|
||
## 代码实现
|
||
|
||
```go
|
||
func productExceptSelf(nums []int) []int {
|
||
n := len(nums)
|
||
answer := make([]int, n)
|
||
|
||
// 步骤1:计算左侧乘积
|
||
answer[0] = 1
|
||
for i := 1; i < n; i++ {
|
||
answer[i] = answer[i-1] * nums[i-1]
|
||
}
|
||
|
||
// 步骤2:计算右侧乘积并更新
|
||
right := 1
|
||
for i := n - 1; i >= 0; i-- {
|
||
answer[i] = answer[i] * right
|
||
right *= nums[i]
|
||
}
|
||
|
||
return answer
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
1. 用 `answer` 存储左侧乘积
|
||
2. 用 `right` 变量累积右侧乘积
|
||
3. 从右向左遍历,边计算边更新
|
||
|
||
---
|
||
|
||
## 执行过程演示
|
||
|
||
**输入**:nums = [1, 2, 3, 4]
|
||
|
||
```
|
||
初始化:answer = [0, 0, 0, 0], right = 1
|
||
|
||
步骤1:计算左侧乘积
|
||
|
||
i=0: answer[0] = 1
|
||
i=1: answer[1] = answer[0] × nums[0] = 1 × 1 = 1
|
||
i=2: answer[2] = answer[1] × nums[1] = 1 × 2 = 2
|
||
i=3: answer[3] = answer[2] × nums[2] = 2 × 3 = 6
|
||
|
||
answer = [1, 1, 2, 6]
|
||
|
||
步骤2:计算右侧乘积并更新
|
||
|
||
i=3: answer[3] = 6 × 1 = 6, right = 1 × 4 = 4
|
||
i=2: answer[2] = 2 × 4 = 8, right = 4 × 3 = 12
|
||
i=1: answer[1] = 1 × 12 = 12, right = 12 × 2 = 24
|
||
i=0: answer[0] = 1 × 24 = 24, right = 24 × 1 = 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 ✓
|
||
```
|
||
|
||
---
|
||
|
||
## 常见错误
|
||
|
||
### 错误1:使用除法
|
||
|
||
❌ **错误代码**:
|
||
```go
|
||
func productExceptSelf(nums []int) []int {
|
||
total := 1
|
||
for _, num := range nums {
|
||
total *= num
|
||
}
|
||
|
||
answer := make([]int, len(nums))
|
||
for i, num := range nums {
|
||
answer[i] = total / num // 错误!题目禁止除法
|
||
}
|
||
return answer
|
||
}
|
||
```
|
||
|
||
**问题**:
|
||
1. 题目明确禁止使用除法
|
||
2. 如果 `num = 0`,除法会出错
|
||
3. 无法处理多个 0 的情况
|
||
|
||
---
|
||
|
||
### 错误2:索引错误
|
||
|
||
❌ **错误代码**:
|
||
```go
|
||
// 错误:包含了当前元素
|
||
answer[i] = answer[i-1] * nums[i] // 错误!
|
||
|
||
// 正确:不包含当前元素
|
||
answer[i] = answer[i-1] * nums[i-1] // 正确
|
||
```
|
||
|
||
**原因**:
|
||
- `answer[i]` 是 nums[i] 左侧的乘积
|
||
- 不应该包含 nums[i] 本身
|
||
|
||
---
|
||
|
||
### 错误3:更新顺序错误
|
||
|
||
❌ **错误代码**:
|
||
```go
|
||
// 错误:先更新了 right
|
||
right *= nums[i]
|
||
answer[i] *= right // right 包含了 nums[i]
|
||
```
|
||
|
||
✅ **正确代码**:
|
||
```go
|
||
// 正确:先使用 right,再更新
|
||
answer[i] *= right
|
||
right *= nums[i]
|
||
```
|
||
|
||
**原因**:
|
||
- `right` 应该是 nums[i] 右侧的乘积
|
||
- 不应该包含 nums[i] 本身
|
||
|
||
---
|
||
|
||
## 进阶问题
|
||
|
||
### Q1: 如果允许使用除法,如何处理 0?
|
||
|
||
**思路**:
|
||
1. 统计 0 的个数
|
||
2. 如果超过 1 个 0,全部为 0
|
||
3. 如果正好 1 个 0,只有该位置为总乘积,其他为 0
|
||
4. 如果没有 0,正常计算
|
||
|
||
```go
|
||
func productExceptSelfWithDivision(nums []int) []int {
|
||
n := len(nums)
|
||
answer := make([]int, n)
|
||
|
||
// 统计 0 的个数和总乘积
|
||
zeroCount := 0
|
||
totalProduct := 1
|
||
for _, num := range nums {
|
||
if num == 0 {
|
||
zeroCount++
|
||
} else {
|
||
totalProduct *= num
|
||
}
|
||
}
|
||
|
||
// 根据零的个数处理
|
||
for i, num := range nums {
|
||
if zeroCount > 1 {
|
||
answer[i] = 0
|
||
} else if zeroCount == 1 {
|
||
if num == 0 {
|
||
answer[i] = totalProduct
|
||
} else {
|
||
answer[i] = 0
|
||
}
|
||
} else {
|
||
answer[i] = totalProduct / num
|
||
}
|
||
}
|
||
|
||
return answer
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Q2: 如何处理大数溢出?
|
||
|
||
**思路**:使用对数转换
|
||
|
||
```go
|
||
func productExceptSelfLog(nums []int) []int {
|
||
n := len(nums)
|
||
logSum := make([]float64, n)
|
||
|
||
// 计算对数和
|
||
for i, num := range nums {
|
||
logSum[i] = math.Log(float64(num))
|
||
}
|
||
|
||
answer := make([]int, n)
|
||
totalLog := 0.0
|
||
for _, log := range logSum {
|
||
totalLog += log
|
||
}
|
||
|
||
// 转换回整数
|
||
for i, log := range logSum {
|
||
answer[i] = int(math.Exp(totalLog - log) + 0.5)
|
||
}
|
||
|
||
return answer
|
||
}
|
||
```
|
||
|
||
**注意**:
|
||
- 对数转换会损失精度
|
||
- 适用于浮点数场景
|
||
- 整数场景需要其他方法
|
||
|
||
---
|
||
|
||
## P7 加分项
|
||
|
||
### 深度理解
|
||
- **分离思想**:将复杂问题分解为简单的子问题
|
||
- **空间优化**:利用输出数组,避免额外空间
|
||
- **递推关系**:左右乘积都可以递推计算
|
||
|
||
### 实战扩展
|
||
- **前缀和/后缀和**:类似思想,用加法代替乘法
|
||
- **范围查询**:线段树、树状数组
|
||
- **业务场景**:计算贡献度、权重分配
|
||
|
||
### 变形题目
|
||
1. [152. 乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/)
|
||
2. 前缀和问题
|
||
3. 区间乘积查询
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
**核心要点**:
|
||
1. **分离思想**:将 answer[i] 分解为左侧 × 右侧
|
||
2. **递推计算**:左侧和右侧都可以递推计算
|
||
3. **空间优化**:用输出数组存储左侧,变量累积右侧
|
||
|
||
**易错点**:
|
||
- 索引错误(是否包含当前元素)
|
||
- 更新顺序错误(先使用还是先更新)
|
||
- 边界条件(0 的处理)
|
||
|
||
**最优解法**:分离左右乘积 + 空间优化,时间 O(n),空间 O(1)
|