按照改进方案,为以下6个二叉树题目增强了解题思路的详细程度: 1. 二叉树的中序遍历 - 增加"思路推导"部分,解释递归到迭代的转换 - 详细说明迭代法的每个步骤 - 增加执行过程演示和多种解法 2. 二叉树的最大深度 - 增加"思路推导",对比DFS和BFS - 详细解释递归的基准情况 - 增加多种解法和变体问题 3. 从前序与中序遍历序列构造二叉树 - 详细解释前序和中序的特点 - 增加"思路推导",说明如何分治 - 详细说明切片边界计算 4. 对称二叉树 - 解释镜像对称的定义 - 详细说明递归比较的逻辑 - 增加迭代解法和变体问题 5. 翻转二叉树 - 解释翻转的定义和过程 - 详细说明多值赋值的执行顺序 - 增加多种解法和有趣的故事 6. 路径总和 - 详细解释路径和叶子节点的定义 - 说明为什么使用递减而非累加 - 增加多种解法和变体问题 每个文件都包含: - 完整的示例和边界条件分析 - 详细的算法流程和图解 - 关键细节说明 - 常见错误分析 - 复杂度分析(详细版) - 执行过程演示 - 多种解法 - 变体问题 - 总结 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
663 lines
14 KiB
Markdown
663 lines
14 KiB
Markdown
# 完全平方数 (Perfect Squares)
|
||
|
||
LeetCode 279. Medium
|
||
|
||
## 题目描述
|
||
|
||
给你一个整数 `n`,返回和为 `n` 的完全平方数的最少数量。
|
||
|
||
**完全平方数**:一个整数等于其平方的整数,如 1, 4, 9, 16 等。
|
||
|
||
**示例 1**:
|
||
```
|
||
输入:n = 12
|
||
输出:3
|
||
解释:12 = 4 + 4 + 4
|
||
```
|
||
|
||
**示例 2**:
|
||
```
|
||
输入:n = 13
|
||
输出:2
|
||
解释:13 = 4 + 9
|
||
```
|
||
|
||
**示例 3**:
|
||
```
|
||
输入:n = 1
|
||
输出:1
|
||
解释:1 = 1
|
||
```
|
||
|
||
## 思路推导
|
||
|
||
### 暴力解法分析
|
||
|
||
**最直观的思路**:尝试所有可能的完全平方数组合。
|
||
|
||
```python
|
||
def numSquares(n):
|
||
if int(n**0.5)**2 == n:
|
||
return 1
|
||
|
||
# 尝试两个数的和
|
||
for i in range(1, int(n**0.5) + 1):
|
||
if int((n - i*i)**0.5)**2 == n - i*i:
|
||
return 2
|
||
|
||
# 尝试三个数的和
|
||
# ...(代码会非常复杂)
|
||
|
||
# 根据拉格朗日四平方定理,最多需要 4 个数
|
||
return 4
|
||
```
|
||
|
||
**时间复杂度**:难以估计,取决于实现
|
||
- 最坏情况可能需要枚举所有组合
|
||
- 代码复杂,难以维护
|
||
|
||
**空间复杂度**:O(1)
|
||
|
||
**问题分析**:
|
||
1. 实现复杂:需要处理多种情况
|
||
2. 难以理解:代码逻辑不清晰
|
||
3. 难以扩展:无法处理变体问题
|
||
|
||
### 优化思考 - 第一步:动态规划
|
||
|
||
**观察**:问题具有最优子结构
|
||
|
||
**关键问题**:如何定义子问题?
|
||
|
||
**定义**:`dp[i]` = 和为 `i` 的完全平方数的最少数量
|
||
|
||
**状态转移方程**:
|
||
```
|
||
dp[i] = min(dp[i - j*j] + 1) for all j where j*j <= i
|
||
```
|
||
|
||
**为什么这样思考?**
|
||
- 如果我们选择了 `j*j`,那么问题变成 `i - j*j`
|
||
- `dp[i - j*j]` 是已知的子问题
|
||
- 我们只需要枚举所有可能的 `j`
|
||
|
||
**优化后的思路**:
|
||
```python
|
||
dp = [0] * (n + 1)
|
||
for i in range(1, n + 1):
|
||
dp[i] = min(dp[i - j*j] + 1 for j in range(1, int(i**0.5) + 1))
|
||
```
|
||
|
||
**时间复杂度**:O(n√n)
|
||
- 外层循环:O(n)
|
||
- 内层循环:O(√n)
|
||
- 总计:O(n) × O(√n) = O(n√n)
|
||
|
||
**空间复杂度**:O(n)
|
||
- dp 数组:O(n)
|
||
|
||
### 优化思考 - 第二步:数学优化(拉格朗日四平方定理)
|
||
|
||
**定理**:任何正整数都可以表示为最多 4 个完全平方数的和。
|
||
|
||
**推论**:
|
||
1. 如果 `n` 是完全平方数,返回 1
|
||
2. 如果 `n` 可以表示为两个完全平方数的和,返回 2
|
||
3. 如果 `n` 满足特定条件(勒让德三平方定理),返回 3
|
||
4. 否则返回 4
|
||
|
||
**为什么这样思考?**
|
||
- 数学定理可以快速判断
|
||
- 避免动态规划的高复杂度
|
||
- 时间复杂度可以降到 O(√n)
|
||
|
||
### 优化思考 - 第三步:BFS 最短路径
|
||
|
||
**观察**:问题可以转化为图的最短路径问题
|
||
|
||
**建模**:
|
||
- 节点:数字 `i`
|
||
- 边:从 `i` 到 `i - j*j`(如果 `j*j <= i`)
|
||
- 权重:每条边的权重都是 1
|
||
|
||
**目标**:从 `n` 到 `0` 的最短路径
|
||
|
||
**为什么这样思考?**
|
||
- BFS 天然适合求最短路径
|
||
- 可以提前终止(找到 0 就停止)
|
||
- 比动态规划更快(实际运行时间)
|
||
|
||
**时间复杂度**:O(√n)^h,h 是答案
|
||
- 最坏情况:h = 4
|
||
- 实际运行:比 O(n√n) 快很多
|
||
|
||
## 解题思路
|
||
|
||
### 核心思想
|
||
|
||
**动态规划**:将问题分解为子问题,从底向上求解。
|
||
|
||
**为什么这样思考?**
|
||
|
||
1. **最优子结构**:
|
||
- `n` 的最优解依赖于 `n - j*j` 的最优解
|
||
- 子问题重叠,可以重复使用
|
||
|
||
2. **无后效性**:
|
||
- `dp[i]` 只依赖于比 `i` 小的值
|
||
- 不依赖于计算路径
|
||
|
||
3. **边界明确**:
|
||
- `dp[0] = 0`(0 个完全平方数)
|
||
- `dp[1] = 1`(1 = 1)
|
||
|
||
### 详细算法流程
|
||
|
||
**步骤1:初始化 dp 数组**
|
||
|
||
```python
|
||
dp = [float('inf')] * (n + 1)
|
||
dp[0] = 0 # 边界条件
|
||
```
|
||
|
||
**作用**:
|
||
- `dp[i]` 表示和为 `i` 的完全平方数的最少数量
|
||
- 初始化为无穷大,表示未计算
|
||
- `dp[0] = 0` 是递归的基础
|
||
|
||
**步骤2:填表**
|
||
|
||
```python
|
||
for i in range(1, n + 1):
|
||
# 尝试所有可能的完全平方数 j*j
|
||
for j in range(1, int(i**0.5) + 1):
|
||
dp[i] = min(dp[i], dp[i - j*j] + 1)
|
||
```
|
||
|
||
**关键点详解**:
|
||
|
||
1. **为什么外层循环从 1 到 n?**
|
||
- 从小到大计算,确保 `dp[i - j*j]` 已经计算过
|
||
- `i - j*j < i`,所以已经计算过
|
||
|
||
2. **为什么内层循环从 1 到 √i?**
|
||
- `j*j` 必须小于等于 `i`
|
||
- 最大的 `j` 是 `√i`
|
||
- 示例:`i = 12`,`j` 最大为 3(3² = 9 ≤ 12)
|
||
|
||
3. **为什么是 `dp[i - j*j] + 1`?**
|
||
- `dp[i - j*j]` 是和为 `i - j*j` 的最少数量
|
||
- 加上 1 表示再加上 `j*j`
|
||
- 示例:`dp[12] = dp[12 - 4] + 1 = dp[8] + 1`
|
||
|
||
**示例**:
|
||
```
|
||
n = 12
|
||
|
||
i=1: dp[1] = dp[1-1] + 1 = dp[0] + 1 = 1
|
||
i=2: dp[2] = dp[2-1] + 1 = dp[1] + 1 = 2
|
||
i=3: dp[3] = dp[3-1] + 1 = dp[2] + 1 = 3
|
||
i=4: dp[4] = dp[4-4] + 1 = dp[0] + 1 = 1
|
||
i=5: dp[5] = min(dp[5-1]+1, dp[5-4]+1) = min(dp[4]+1, dp[1]+1) = min(2, 2) = 2
|
||
...
|
||
i=12: dp[12] = min(dp[12-1]+1, dp[12-4]+1, dp[12-9]+1)
|
||
= min(dp[11]+1, dp[8]+1, dp[3]+1)
|
||
= min(3, 2, 4)
|
||
= 2
|
||
```
|
||
|
||
### 关键细节说明
|
||
|
||
**细节1:为什么 `dp[0] = 0`?**
|
||
|
||
```python
|
||
dp[0] = 0 # 0 可以由 0 个完全平方数组成
|
||
```
|
||
|
||
**原因**:
|
||
- 0 是递归的终止条件
|
||
- 表示不需要任何完全平方数就能组成 0
|
||
- 类似于数学中的"空和为 0"
|
||
|
||
**细节2:为什么初始化为 `float('inf')`?**
|
||
|
||
```python
|
||
dp = [float('inf')] * (n + 1)
|
||
dp[0] = 0
|
||
```
|
||
|
||
**原因**:
|
||
- 无穷大表示"未计算"或"不可达"
|
||
- `min(inf, x) = x`,确保第一次更新正确
|
||
- 避免使用 0 初始化导致 `min(0, x)` 错误
|
||
|
||
**细节3:为什么用 `int(i**0.5) + 1`?**
|
||
|
||
```python
|
||
for j in range(1, int(i**0.5) + 1):
|
||
# ...
|
||
```
|
||
|
||
**原因**:
|
||
- `int(i**0.5)` 是 `√i` 的整数部分
|
||
- `+1` 是因为 `range` 是左闭右开区间
|
||
- 示例:`i = 4`,`int(4**0.5) = 2`,`range(1, 3)` → `[1, 2]` ✓
|
||
|
||
**细节4:为什么可以保证最优解?**
|
||
|
||
```python
|
||
dp[i] = min(dp[i - j*j] + 1 for all valid j)
|
||
```
|
||
|
||
**原因**:
|
||
- 枚举了所有可能的第一个完全平方数 `j*j`
|
||
- `dp[i - j*j]` 已经是最优解(归纳假设)
|
||
- `min` 确保选择了最优的组合
|
||
- 动态规划的"最优子结构"性质
|
||
|
||
### 边界条件分析
|
||
|
||
**边界1:n = 0**
|
||
|
||
```
|
||
输入:n = 0
|
||
输出:0
|
||
解释:
|
||
dp[0] = 0
|
||
0 个完全平方数组成 0
|
||
```
|
||
|
||
**边界2:n = 1**
|
||
|
||
```
|
||
输入:n = 1
|
||
输出:1
|
||
过程:
|
||
dp[1] = dp[1-1] + 1 = dp[0] + 1 = 1
|
||
```
|
||
|
||
**边界3:n 是完全平方数**
|
||
|
||
```
|
||
输入:n = 9
|
||
输出:1
|
||
过程:
|
||
dp[9] = min(dp[9-1]+1, dp[9-4]+1, dp[9-9]+1)
|
||
= min(dp[8]+1, dp[5]+1, dp[0]+1)
|
||
= min(3, 3, 1)
|
||
= 1
|
||
```
|
||
|
||
**边界4:n = 12**
|
||
|
||
```
|
||
输入:n = 12
|
||
输出:3
|
||
过程:
|
||
dp[12] = min(dp[12-1]+1, dp[12-4]+1, dp[12-9]+1)
|
||
= min(dp[11]+1, dp[8]+1, dp[3]+1)
|
||
= min(4, 3, 4)
|
||
= 3
|
||
|
||
验证:12 = 4 + 4 + 4 ✓
|
||
```
|
||
|
||
### 复杂度分析(详细版)
|
||
|
||
**时间复杂度**:
|
||
```
|
||
- 外层循环:O(n),从 1 到 n
|
||
- 内层循环:O(√n),从 1 到 √i
|
||
- 总计:O(n) × O(√n) = O(n√n)
|
||
|
||
为什么是 O(n√n)?
|
||
- 对于每个 i,最多有 √i 个 j
|
||
- √i ≤ √n
|
||
- 总操作次数 ≈ n × √n = n√n
|
||
```
|
||
|
||
**空间复杂度**:
|
||
```
|
||
- dp 数组:O(n)
|
||
- 其他变量:O(1)
|
||
- 总计:O(n)
|
||
```
|
||
|
||
---
|
||
|
||
## 图解过程
|
||
|
||
```
|
||
n = 12
|
||
|
||
初始化:dp = [0, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf]
|
||
|
||
i=1: j=1
|
||
dp[1] = dp[1-1] + 1 = dp[0] + 1 = 1
|
||
dp = [0, 1, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf]
|
||
|
||
i=2: j=1
|
||
dp[2] = dp[2-1] + 1 = dp[1] + 1 = 2
|
||
dp = [0, 1, 2, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf]
|
||
|
||
i=3: j=1
|
||
dp[3] = dp[3-1] + 1 = dp[2] + 1 = 3
|
||
dp = [0, 1, 2, 3, inf, inf, inf, inf, inf, inf, inf, inf, inf]
|
||
|
||
i=4: j=1,2
|
||
dp[4] = min(dp[4-1]+1, dp[4-4]+1) = min(dp[3]+1, dp[0]+1) = min(4, 1) = 1
|
||
dp = [0, 1, 2, 3, 1, inf, inf, inf, inf, inf, inf, inf, inf]
|
||
|
||
i=5: j=1,2
|
||
dp[5] = min(dp[5-1]+1, dp[5-4]+1) = min(dp[4]+1, dp[1]+1) = min(2, 2) = 2
|
||
dp = [0, 1, 2, 3, 1, 2, inf, inf, inf, inf, inf, inf, inf]
|
||
|
||
i=6: j=1,2
|
||
dp[6] = min(dp[6-1]+1, dp[6-4]+1) = min(dp[5]+1, dp[2]+1) = min(3, 3) = 3
|
||
dp = [0, 1, 2, 3, 1, 2, 3, inf, inf, inf, inf, inf, inf]
|
||
|
||
i=7: j=1,2
|
||
dp[7] = min(dp[7-1]+1, dp[7-4]+1) = min(dp[6]+1, dp[3]+1) = min(4, 4) = 4
|
||
dp = [0, 1, 2, 3, 1, 2, 3, 4, inf, inf, inf, inf, inf]
|
||
|
||
i=8: j=1,2
|
||
dp[8] = min(dp[8-1]+1, dp[8-4]+1) = min(dp[7]+1, dp[4]+1) = min(5, 2) = 2
|
||
dp = [0, 1, 2, 3, 1, 2, 3, 4, 2, inf, inf, inf, inf]
|
||
|
||
i=9: j=1,2,3
|
||
dp[9] = min(dp[9-1]+1, dp[9-4]+1, dp[9-9]+1) = min(dp[8]+1, dp[5]+1, dp[0]+1) = min(3, 3, 1) = 1
|
||
dp = [0, 1, 2, 3, 1, 2, 3, 4, 2, 1, inf, inf, inf]
|
||
|
||
i=10: j=1,2,3
|
||
dp[10] = min(dp[10-1]+1, dp[10-4]+1, dp[10-9]+1) = min(dp[9]+1, dp[6]+1, dp[1]+1) = min(2, 4, 2) = 2
|
||
dp = [0, 1, 2, 3, 1, 2, 3, 4, 2, 1, 2, inf, inf]
|
||
|
||
i=11: j=1,2,3
|
||
dp[11] = min(dp[11-1]+1, dp[11-4]+1, dp[11-9]+1) = min(dp[10]+1, dp[7]+1, dp[2]+1) = min(3, 5, 3) = 3
|
||
dp = [0, 1, 2, 3, 1, 2, 3, 4, 2, 1, 2, 3, inf]
|
||
|
||
i=12: j=1,2,3
|
||
dp[12] = min(dp[12-1]+1, dp[12-4]+1, dp[12-9]+1) = min(dp[11]+1, dp[8]+1, dp[3]+1) = min(4, 3, 4) = 3
|
||
dp = [0, 1, 2, 3, 1, 2, 3, 4, 2, 1, 2, 3, 3]
|
||
|
||
结果:dp[12] = 3
|
||
```
|
||
|
||
---
|
||
|
||
## 代码实现
|
||
|
||
```go
|
||
func numSquares(n int) int {
|
||
dp := make([]int, n+1)
|
||
|
||
// 初始化为最大值
|
||
for i := range dp {
|
||
dp[i] = math.MaxInt32
|
||
}
|
||
|
||
dp[0] = 0
|
||
|
||
// 填表
|
||
for i := 1; i <= n; i++ {
|
||
for j := 1; j*j <= i; j++ {
|
||
if dp[i-j*j]+1 < dp[i] {
|
||
dp[i] = dp[i-j*j] + 1
|
||
}
|
||
}
|
||
}
|
||
|
||
return dp[n]
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
1. `dp[0] = 0` 是边界条件
|
||
2. 从小到大计算,确保子问题已解决
|
||
3. 枚举所有可能的完全平方数
|
||
|
||
---
|
||
|
||
## 执行过程演示
|
||
|
||
**输入**:n = 12
|
||
|
||
```
|
||
初始化:dp = [0, 2147483647, 2147483647, 2147483647, 2147483647, 2147483647, 2147483647, 2147483647, 2147483647, 2147483647, 2147483647, 2147483647, 2147483647]
|
||
|
||
i=1: j=1
|
||
dp[1] = min(2147483647, dp[0]+1) = 1
|
||
|
||
i=2: j=1
|
||
dp[2] = min(2147483647, dp[1]+1) = 2
|
||
|
||
i=3: j=1
|
||
dp[3] = min(2147483647, dp[2]+1) = 3
|
||
|
||
i=4: j=1,2
|
||
j=1: dp[4] = min(2147483647, dp[3]+1) = 4
|
||
j=2: dp[4] = min(4, dp[0]+1) = 1
|
||
|
||
i=5: j=1,2
|
||
j=1: dp[5] = min(2147483647, dp[4]+1) = 2
|
||
j=2: dp[5] = min(2, dp[1]+1) = 2
|
||
|
||
i=6: j=1,2
|
||
j=1: dp[6] = min(2147483647, dp[5]+1) = 3
|
||
j=2: dp[6] = min(3, dp[2]+1) = 3
|
||
|
||
i=7: j=1,2
|
||
j=1: dp[7] = min(2147483647, dp[6]+1) = 4
|
||
j=2: dp[7] = min(4, dp[3]+1) = 4
|
||
|
||
i=8: j=1,2
|
||
j=1: dp[8] = min(2147483647, dp[7]+1) = 5
|
||
j=2: dp[8] = min(5, dp[4]+1) = 2
|
||
|
||
i=9: j=1,2,3
|
||
j=1: dp[9] = min(2147483647, dp[8]+1) = 3
|
||
j=2: dp[9] = min(3, dp[5]+1) = 3
|
||
j=3: dp[9] = min(3, dp[0]+1) = 1
|
||
|
||
i=10: j=1,2,3
|
||
j=1: dp[10] = min(2147483647, dp[9]+1) = 2
|
||
j=2: dp[10] = min(2, dp[6]+1) = 2
|
||
j=3: dp[10] = min(2, dp[1]+1) = 2
|
||
|
||
i=11: j=1,2,3
|
||
j=1: dp[11] = min(2147483647, dp[10]+1) = 3
|
||
j=2: dp[11] = min(3, dp[7]+1) = 3
|
||
j=3: dp[11] = min(3, dp[2]+1) = 3
|
||
|
||
i=12: j=1,2,3
|
||
j=1: dp[12] = min(2147483647, dp[11]+1) = 4
|
||
j=2: dp[12] = min(4, dp[8]+1) = 3
|
||
j=3: dp[12] = min(3, dp[3]+1) = 3
|
||
|
||
结果:dp[12] = 3
|
||
```
|
||
|
||
---
|
||
|
||
## 常见错误
|
||
|
||
### 错误1:初始化为 0
|
||
|
||
❌ **错误代码**:
|
||
```go
|
||
dp := make([]int, n+1)
|
||
// dp 默认为 0
|
||
|
||
for i := 1; i <= n; i++ {
|
||
for j := 1; j*j <= i; j++ {
|
||
dp[i] = min(dp[i], dp[i-j*j]+1) // 错误!dp[i] 初始为 0
|
||
}
|
||
}
|
||
```
|
||
|
||
✅ **正确代码**:
|
||
```go
|
||
dp := make([]int, n+1)
|
||
for i := range dp {
|
||
dp[i] = math.MaxInt32 // 初始化为最大值
|
||
}
|
||
dp[0] = 0
|
||
```
|
||
|
||
**原因**:
|
||
- `dp[i]` 初始为 0,`min(0, x)` 永远是 0
|
||
- 导致结果错误
|
||
|
||
---
|
||
|
||
### 错误2:循环范围错误
|
||
|
||
❌ **错误代码**:
|
||
```go
|
||
for j := 1; j <= int(math.Sqrt(float64(n))); j++ {
|
||
// 错误!用了 n 而不是 i
|
||
}
|
||
```
|
||
|
||
✅ **正确代码**:
|
||
```go
|
||
for j := 1; j*j <= i; j++ {
|
||
// 正确!用 i
|
||
}
|
||
```
|
||
|
||
**原因**:
|
||
- 应该是 `j*j <= i`,不是 `j*j <= n`
|
||
- 用 n 会导致越界或计算错误
|
||
|
||
---
|
||
|
||
### 错误3:忘记边界条件
|
||
|
||
❌ **错误代码**:
|
||
```go
|
||
dp := make([]int, n+1)
|
||
// 忘记设置 dp[0] = 0
|
||
```
|
||
|
||
✅ **正确代码**:
|
||
```go
|
||
dp := make([]int, n+1)
|
||
dp[0] = 0 // 边界条件
|
||
```
|
||
|
||
**原因**:
|
||
- `dp[0] = 0` 是递归的基础
|
||
- 没有它,`dp[i-j*j]` 无法正确计算
|
||
|
||
---
|
||
|
||
## 进阶问题
|
||
|
||
### Q1: 如何优化空间复杂度?
|
||
|
||
**思路**:使用贪心算法或数学定理
|
||
|
||
```go
|
||
// 基于拉格朗日四平方定理
|
||
func numSquaresOptimized(n int) int {
|
||
// 检查是否为完全平方数
|
||
if isPerfectSquare(n) {
|
||
return 1
|
||
}
|
||
|
||
// 检查是否可以表示为两个完全平方数的和
|
||
for i := 1; i*i <= n; i++ {
|
||
if isPerfectSquare(n - i*i) {
|
||
return 2
|
||
}
|
||
}
|
||
|
||
// 检查是否满足勒让德三平方定理
|
||
// n != 4^a * (8b + 7)
|
||
if n == 4*n {
|
||
return 4
|
||
}
|
||
|
||
return 3
|
||
}
|
||
|
||
func isPerfectSquare(n int) bool {
|
||
sqrt := int(math.Sqrt(float64(n)))
|
||
return sqrt*sqrt == n
|
||
}
|
||
```
|
||
|
||
**时间复杂度**:O(√n)
|
||
|
||
---
|
||
|
||
### Q2: 如何返回具体的完全平方数组合?
|
||
|
||
```go
|
||
func numSquaresWithSolution(n int) ([]int, int) {
|
||
dp := make([]int, n+1)
|
||
prev := make([]int, n+1) // 记录前一个状态
|
||
|
||
for i := range dp {
|
||
dp[i] = math.MaxInt32
|
||
}
|
||
dp[0] = 0
|
||
|
||
for i := 1; i <= n; i++ {
|
||
for j := 1; j*j <= i; j++ {
|
||
if dp[i-j*j]+1 < dp[i] {
|
||
dp[i] = dp[i-j*j] + 1
|
||
prev[i] = j * j // 记录选择的完全平方数
|
||
}
|
||
}
|
||
}
|
||
|
||
// 回溯构建解
|
||
solution := []int{}
|
||
curr := n
|
||
for curr > 0 {
|
||
square := prev[curr]
|
||
solution = append(solution, square)
|
||
curr -= square
|
||
}
|
||
|
||
return solution, dp[n]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## P7 加分项
|
||
|
||
### 深度理解
|
||
- **最优子结构**:问题的最优解包含子问题的最优解
|
||
- **重叠子问题**:子问题会被重复计算,动态规划避免重复
|
||
- **数学定理**:拉格朗日四平方定理可以优化算法
|
||
|
||
### 实战扩展
|
||
- **背包问题**:完全背包的变种
|
||
- **最短路径**:可以转化为 BFS 问题
|
||
- **业务场景**:货币兑换、资源分配
|
||
|
||
### 变形题目
|
||
1. 最少硬币数量(硬币面值不同)
|
||
2. 完全平方数的所有组合
|
||
3. 限制完全平方数的使用次数
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
**核心要点**:
|
||
1. **动态规划**:从底向上,逐步求解
|
||
2. **状态转移**:`dp[i] = min(dp[i - j*j] + 1)`
|
||
3. **边界条件**:`dp[0] = 0`
|
||
|
||
**易错点**:
|
||
- 初始化错误(0 vs MaxInt32)
|
||
- 循环范围错误(n vs i)
|
||
- 忘记边界条件
|
||
|
||
**最优解法**:动态规划,时间 O(n√n),空间 O(n)
|