|
5 | 5 |
|
6 | 6 | ## 题目大意
|
7 | 7 |
|
8 |
| -给定一个正整数 n,找到若干个完全平方数(比如 1,4,9,16,...),使得它们的和等于 n。要求返回和为 n 的完全平方数的最小数量。 |
| 8 | +**描述**:给定一个正整数 `n`。从中找到若干个完全平方数(比如 `1`、`4`、`1`、`16` ...),使得它们的和等于 `n`。 |
| 9 | + |
| 10 | +**要求**:返回和为 `n` 的完全平方数的最小数量。 |
| 11 | + |
| 12 | +**说明**: |
| 13 | + |
| 14 | +- $1 \le n \le 10^4$。 |
| 15 | + |
| 16 | +**示例**: |
| 17 | + |
| 18 | +```Python |
| 19 | +输入:n = 12 |
| 20 | +输出:3 |
| 21 | +解释:12 = 4 + 4 + 4 |
| 22 | + |
| 23 | + |
| 24 | +输入:n = 13 |
| 25 | +输出:2 |
| 26 | +解释:13 = 4 + 9 |
| 27 | +``` |
9 | 28 |
|
10 | 29 | ## 解题思路
|
11 | 30 |
|
12 |
| -对于小于 n 的完全平方数,直接暴力枚举所有可能的组合,并且找到平方数个数最小的一个。 |
| 31 | +对于小于 `n` 的完全平方数,直接暴力枚举所有可能的组合,并且找到平方数个数最小的一个。 |
13 | 32 |
|
14 |
| -并且对于所有小于 n 的完全平方数(k = 1,4,9,16,...),存在公式 $ans(n) = min(ans(n-k) + 1),k = 1,4,9,16,...$ |
| 33 | +并且对于所有小于 `n` 的完全平方数(`k = 1, 4, 9, 16, ...`),存在公式 $ans(n) = min(ans(n - k) + 1),k = 1,4,9,16,...$ |
15 | 34 |
|
16 |
| -即: **n 的完全平方数的最小数量** 等于 **n - k 的完全平方数的最小数量 + 1**。 |
| 35 | +即: **n 的完全平方数的最小数量 == n - k 的完全平方数的最小数量 + 1**。 |
17 | 36 |
|
18 |
| -可以转为递归解决这个问题。但是因为重复计算了中间解,会产生堆栈溢出。 |
| 37 | +我们可以使用递归解决这个问题。但是因为重复计算了中间解,会产生堆栈溢出。 |
19 | 38 |
|
20 |
| -怎么解决重复计算问题和避免堆栈溢出? |
| 39 | +那怎么解决重复计算问题和避免堆栈溢出? |
21 | 40 |
|
22 |
| -将 n 作为根节点,构建一棵多叉数。从 n 节点出发,如果一个小于 n 的数刚好与 n 相差一个平方数,则以该数为值构造一个节点,与 n 相连。 |
| 41 | +我们可转换一下思维。 |
23 | 42 |
|
24 |
| -那么求解和为 n 的完全平方数的最小数量就变成了求解这棵树从 n 节点到 0 节点的最短路径,或者说数的最小深度。 |
| 43 | +1. 将 `n` 作为根节点,构建一棵多叉数。 |
| 44 | +2. 从 `n` 节点出发,如果一个小于 `n` 的数刚好与 `n` 相差一个平方数,则以该数为值构造一个节点,与 `n` 相连。 |
25 | 45 |
|
26 |
| -首先,我们将小于 n 的平方数放入数组中。然后使用「广度优先搜索」的方式,每次从当前节点值减去一个平方数,将减完的数加入队列。 |
| 46 | +那么求解和为 `n` 的完全平方数的最小数量就变成了求解这棵树从 `n` 节点到 `0` 节点的最短路径,或者说数的最小深度。 |
27 | 47 |
|
28 |
| -- 如果此时的数等于 0,则满足题意,返回层数。 |
29 |
| -- 如果此时的数不等于 0,则将其加入队列,继续查找。 |
| 48 | +这个过程可以通过广度优先搜索来做。 |
30 | 49 |
|
31 |
| -但是还有个问题:如何减少重复计算的次数。 |
| 50 | +### 思路 1:广度优先搜索 |
32 | 51 |
|
33 |
| -我们可以使用一个 set 集合,来消除同一层中相同的数,这样就减少了计算次数。 |
| 52 | +1. 定义 `visited` 为标记访问节点的 set 集合变量,避免重复计算。定义 `queue` 为存放节点的队列。使用 `count` 表示和为 `n` 的完全平方数的最小数量。 |
| 53 | +2. 首先,我们将 `n` 标记为已访问,即 `visited.add(n)`。并将其加入队列 `queue` 中,即 `queue.append(n)`。 |
| 54 | +3. 令 `count` 加 `1`,表示最小深度加 `1`。然后依次将队列中的节点值取出。 |
| 55 | +4. 对于取出的节点值 `value`,遍历可能出现的平方数(即遍历 $[1, \sqrt{value} + 1]$ 中的数)。 |
| 56 | +5. 每次从当前节点值减去一个平方数,并将减完的数加入队列。 |
| 57 | + 1. 如果此时的数等于 `0`,则满足题意,返回层数。 |
| 58 | + 2. 如果此时的数不等于 `0`,则将其加入队列,继续查找。 |
34 | 59 |
|
35 |
| -## 代码 |
| 60 | +### 思路 1:代码 |
36 | 61 |
|
37 | 62 | ```Python
|
38 | 63 | class Solution:
|
39 | 64 | def numSquares(self, n: int) -> int:
|
40 | 65 | if n == 0:
|
41 | 66 | return 0
|
42 |
| - queue = collections.deque([n]) |
| 67 | + |
43 | 68 | visited = set()
|
44 |
| - level = 0 |
| 69 | + queue = collections.deque([]) |
| 70 | + |
| 71 | + visited.add(n) |
| 72 | + queue.append(n) |
| 73 | + |
| 74 | + count = 0 |
45 | 75 | while queue:
|
46 |
| - level += 1 |
| 76 | + // 最少步数 |
| 77 | + count += 1 |
47 | 78 | size = len(queue)
|
48 | 79 | for _ in range(size):
|
49 |
| - top = queue.pop() |
50 |
| - for i in range(1, int(math.sqrt(top)) + 1): |
51 |
| - x = top - i * i |
| 80 | + value = queue.pop() |
| 81 | + for i in range(1, int(math.sqrt(value)) + 1): |
| 82 | + x = value - i * i |
52 | 83 | if x == 0:
|
53 |
| - return level |
| 84 | + return count |
54 | 85 | if x not in visited:
|
55 | 86 | queue.appendleft(x)
|
56 | 87 | visited.add(x)
|
57 | 88 | ```
|
58 | 89 |
|
| 90 | +### 思路 1:复杂度分析 |
| 91 | + |
| 92 | +- **时间复杂度**:$O(n \times \sqrt{n})$。 |
| 93 | +- **空间复杂度**:$O(n)$。 |
| 94 | + |
0 commit comments