|
1 | 1 | ## 1. 枚举算法简介
|
2 | 2 |
|
3 | | -> **枚举算法(Enumeration Algorithm)**:也称为穷举算法,指的是按照问题本身的性质,一一列举出该问题所有可能的解,并在逐一列举的过程中,将它们逐一与目标状态进行比较以得出满足问题要求的解。在列举的过程中,既不能遗漏也不能重复。 |
| 3 | +> **枚举算法(Enumeration Algorithm)**,又称穷举算法,是指根据问题的特点,逐一列出所有可能的解,并与目标条件进行比较,找出满足要求的答案。枚举时要确保不遗漏、不重复。 |
4 | 4 |
|
5 | | -枚举算法的核心思想是:通过列举问题的所有状态,将它们逐一与目标状态进行比较,从而得到满足条件的解。 |
| 5 | +枚举算法的核心思想就是:遍历所有可能的状态,逐个判断是否满足条件,找到符合要求的解。 |
6 | 6 |
|
7 | | -由于枚举算法要通过列举问题的所有状态来得到满足条件的解,因此,在问题规模变大时,其效率一般是比较低的。但是枚举算法也有自己特有的优点: |
| 7 | +由于需要遍历所有状态,枚举算法在问题规模较大时效率较低。但它也有明显优点: |
8 | 8 |
|
9 | | -1. 多数情况下容易编程实现,也容易调试。 |
10 | | -2. 建立在考察大量状态、甚至是穷举所有状态的基础上,所以算法的正确性比较容易证明。 |
| 9 | +1. 实现简单,易于编程和调试。 |
| 10 | +2. 基于穷举所有情况,正确性容易验证。 |
11 | 11 |
|
12 | | -所以,枚举算法通常用于求解问题规模比较小的问题,或者作为求解问题的一个子算法出现,通过枚举一些信息并进行保存,而这些消息的有无对主算法效率的高低有着较大影响。 |
| 12 | +因此,枚举算法常用于小规模问题,或作为其他算法的辅助工具,通过枚举部分信息来提升主算法的效率。 |
13 | 13 |
|
14 | 14 | ## 2. 枚举算法的解题思路
|
15 | 15 |
|
16 | 16 | ### 2.1 枚举算法的解题思路
|
17 | 17 |
|
18 | | -枚举算法是设计最简单、最基本的搜索算法。是我们在遇到问题时,最应该优先考虑的算法。 |
| 18 | +枚举算法是最简单、最基础的搜索方法,通常是遇到问题时的首选方案。 |
19 | 19 |
|
20 | | -因为其实现足够简单,所以在遇到问题时,我们往往可以先通过枚举算法尝试解决问题,然后在此基础上,再去考虑其他优化方法和解题思路。 |
| 20 | +由于实现简单,我们可以先用枚举算法尝试解决问题,再考虑是否需要优化。 |
21 | 21 |
|
22 | | -采用枚举算法解题的一般思路如下: |
| 22 | +枚举算法的基本步骤如下: |
23 | 23 |
|
24 | | -1. 确定枚举对象、枚举范围和判断条件,并判断条件设立的正确性。 |
25 | | -2. 一一枚举可能的情况,并验证是否是问题的解。 |
26 | | -3. 考虑提高枚举算法的效率。 |
| 24 | +1. 明确需要枚举的对象、枚举范围和约束条件。 |
| 25 | +2. 逐一枚举所有可能情况,判断是否满足题意。 |
| 26 | +3. 思考如何提升枚举效率。 |
27 | 27 |
|
28 | | -我们可以从下面几个方面考虑提高算法的效率: |
| 28 | +提升效率的常用方法有: |
29 | 29 |
|
30 | | -1. 抓住问题状态的本质,尽可能缩小问题状态空间的大小。 |
31 | | -2. 加强约束条件,缩小枚举范围。 |
32 | | -3. 根据某些问题特有的性质,例如对称性等,避免对本质相同的状态重复求解。 |
| 30 | +- 抓住问题本质,尽量缩小状态空间。 |
| 31 | +- 增加约束条件,减少无效枚举。 |
| 32 | +- 利用某些问题特有的性质(例如对称性等),避免重复计算。 |
33 | 33 |
|
34 | 34 | ### 2.2 枚举算法的简单应用
|
35 | 35 |
|
36 | | -下面举个著名的例子:「百钱买百鸡问题」。这个问题是我国古代数学家张丘在「算经」一书中提出的。该问题叙述如下: |
| 36 | +以经典的「百钱买百鸡问题」为例: |
37 | 37 |
|
38 | | -> **百钱买百鸡问题**:鸡翁一,值钱五;鸡母一,值钱三;鸡雏三,值钱一;百钱买百鸡,则鸡翁、鸡母、鸡雏各几何? |
| 38 | +> **问题**:公鸡 5 元/只,母鸡 3 元/只,小鸡 1 元/3 只。用 100 元买 100 只鸡,问各买多少只? |
39 | 39 |
|
40 | | -翻译一下,意思就是:公鸡一只五块钱,母鸡一只三块钱,小鸡三只一块钱。现在我们用 100ドル$ 块钱买了 100ドル$ 只鸡,问公鸡、母鸡、小鸡各买了多少只? |
| 40 | +**解题步骤**: |
41 | 41 |
|
42 | | -下面我们根据算法的一般思路来解决一下这道题。 |
| 42 | +1. **确定枚举对象和范围** |
| 43 | + - 枚举对象:公鸡数 $x,ドル母鸡数 $y,ドル小鸡数 $z$ |
| 44 | + - 枚举范围:0ドル \le x, y, z \le 100$ |
| 45 | + - 约束条件:5ドルx + 3y + \frac{z}{3} = 100$ 且 $x + y + z = 100$ |
43 | 46 |
|
44 | | -1. 确定枚举对象、枚举范围和判断条件,并判断条件设立的正确性。 |
| 47 | +2.**暴力枚举** |
45 | 48 |
|
46 | | - 1. 确定枚举对象:枚举对象为公鸡、母鸡、小鸡的只数,那么我们可以用变量 $x$、$y$、$z$ 分别来代表公鸡、母鸡、小鸡的只数。 |
47 | | - 2. 确定枚举范围:因为总共买了 100ドル$ 只鸡,所以 0ドル \le x, y, z \le 100,ドル则 $x$、$y$、$z$ 的枚举范围为 $[0, 100]$。 |
48 | | - 3. 确定判断条件:根据题意,我们可以列出两个方程式:5ドル \times x + 3 \times y + \frac{z}{3} = 100,ドル$x + y + z = 100$。在枚举 $x$、$y$、$z$ 的过程中,我们可以根据这两个方程式来判断是否当前状态是否满足题意。 |
| 49 | + ```python |
| 50 | + class Solution: |
| 51 | + def buyChicken(self): |
| 52 | + for x in range(101): |
| 53 | + for y in range(101): |
| 54 | + for z in range(101): |
| 55 | + if z % 3 == 0 and 5 * x + 3 * y + z // 3 == 100 and x + y + z == 100: |
| 56 | + print("公鸡 %s 只,母鸡 %s 只,小鸡 %s 只" % (x, y, z)) |
| 57 | + ``` |
49 | 58 |
|
50 | | -2. 一一枚举可能的情况,并验证是否是问题的解。 |
| 59 | +3. **优化枚举效率** |
| 60 | + - 利用 $z = 100 - x - y$ 减少一重循环 |
| 61 | + - 缩小枚举范围:$x \in [0, 20],ドル$y \in [0, 33]$ |
51 | 62 |
|
52 | | - 1. 根据枚举对象、枚举范围和判断条件,我们可以顺利写出对应的代码。 |
53 | | - |
54 | | - ```python |
55 | | - class Solution: |
56 | | - def buyChicken(self): |
57 | | - for x in range(101): |
58 | | - for y in range(101): |
59 | | - for z in range(101): |
60 | | - if z % 3 == 0 and 5 * x + 3 * y + z // 3 == 100 and x + y + z == 100: |
61 | | - print("公鸡 %s 只,母鸡 %s 只,小鸡 %s 只" % (x, y, z)) |
62 | | - ``` |
63 | | - |
64 | | -3. 考虑提高枚举算法的效率。 |
65 | | - |
66 | | - 1. 在上面的代码中,我们枚举了 $x$、$y$、$z$,但其实根据方程式 $x + y + z = 100$,得知:$z$ 可以通过 $z = 100 - x - y$ 而得到,这样我们就不用再枚举 $z$ 了。 |
67 | | - 2. 在上面的代码中,对 $x$、$y$ 的枚举范围是 $[0, 100]$,但其实如果所有钱用来买公鸡,最多只能买 $20$ 只,同理,全用来买母鸡,最多只能买 $33$ 只。所以对 $x$ 的枚举范围可改为 $[0, 20]$,$y$ 的枚举范围可改为 $[0, 33]$。 |
68 | | - |
69 | | - ```python |
70 | | - class Solution: |
71 | | - def buyChicken(self): |
72 | | - for x in range(21): |
73 | | - for y in range(34): |
74 | | - z = 100 - x - y |
75 | | - if z % 3 == 0 and 5 * x + 3 * y + z // 3 == 100: |
76 | | - print("公鸡 %s 只,母鸡 %s 只,小鸡 %s 只" % (x, y, z)) |
77 | | - ``` |
| 63 | + ```python |
| 64 | + class Solution: |
| 65 | + def buyChicken(self): |
| 66 | + for x in range(21): |
| 67 | + for y in range(34): |
| 68 | + z = 100 - x - y |
| 69 | + if z % 3 == 0 and 5 * x + 3 * y + z // 3 == 100: |
| 70 | + print("公鸡 %s 只,母鸡 %s 只,小鸡 %s 只" % (x, y, z)) |
| 71 | + ``` |
78 | 72 |
|
79 | 73 |
|
80 | 74 | ## 3. 枚举算法的应用
|
81 | 75 |
|
82 | | -### 3.1 两数之和 |
| 76 | +### 3.1 经典例题:两数之和 |
83 | 77 |
|
84 | 78 | #### 3.1.1 题目链接
|
85 | 79 |
|
|
117 | 111 |
|
118 | 112 | #### 3.1.3 解题思路
|
119 | 113 |
|
120 | | -这里说下枚举算法的解题思路。 |
121 | | - |
122 | 114 | ##### 思路 1:枚举算法
|
123 | 115 |
|
124 | | -1. 使用两重循环枚举数组中每一个数 $nums[i]$、$nums[j],ドル判断所有的 $nums[i] + nums[j]$ 是否等于 $target$。 |
125 | | -2. 如果出现 $nums[i] + nums[j] == target,ドル则说明数组中存在和为 $target$ 的两个整数,将两个整数的下标 $i$、$j$ 输出即可。 |
| 116 | +1. 通过两重循环,依次枚举数组中所有可能的下标对 $(i, j)$(其中 $i < j$),判断 $nums[i] + nums[j]$ 是否等于 $target$。 |
| 117 | +2. 一旦找到满足条件的下标对,即 $nums[i] + nums[j] == target,ドル立即返回这两个下标 $[i, j]$ 作为答案。 |
126 | 118 |
|
127 | 119 | ##### 思路 1:代码
|
128 | 120 |
|
129 | 121 | ```python
|
130 | 122 | class Solution:
|
131 | 123 | def twoSum(self, nums: List[int], target: int) -> List[int]:
|
| 124 | + # 遍历第一个数的下标 |
132 | 125 | for i in range(len(nums)):
|
| 126 | + # 遍历第二个数的下标(只需从i+1开始,避免和自身重复) |
133 | 127 | for j in range(i + 1, len(nums)):
|
134 | | - if i != j and nums[i] + nums[j] == target: |
135 | | - return [i, j] |
136 | | - return [] |
| 128 | + # 判断两数之和是否等于目标值 |
| 129 | + if nums[i] + nums[j] == target: |
| 130 | + return [i, j] # 返回下标对 |
| 131 | + return [] # 如果没有找到,返回空列表 |
137 | 132 | ```
|
138 | 133 |
|
139 | 134 | ##### 思路 1:复杂度分析
|
140 | 135 |
|
141 | 136 | - **时间复杂度**:$O(n^2),ドル其中 $n$ 为数组 $nums$ 的元素数量。
|
142 | 137 | - **空间复杂度**:$O(1)$。
|
143 | 138 |
|
144 | | -### 3.2 计数质数 |
| 139 | +### 3.2 统计平方和三元组的数目 |
145 | 140 |
|
146 | 141 | #### 3.2.1 题目链接
|
147 | 142 |
|
148 | | -- [204. 计数质数 - 力扣(LeetCode)](https://leetcode.cn/problems/count-primes/) |
149 | | - |
150 | | -#### 3.2.2 题目大意 |
151 | | - |
152 | | -**描述**:给定 一个非负整数 $n$。 |
153 | | - |
154 | | -**要求**:统计小于 $n$ 的质数数量。 |
155 | | - |
156 | | -**说明**: |
157 | | - |
158 | | -- 0ドル \le n \le 5 \times 10^6$。 |
159 | | - |
160 | | -**示例**: |
161 | | - |
162 | | -- 示例 1: |
163 | | - |
164 | | -```python |
165 | | -输入 n = 10 |
166 | | -输出 4 |
167 | | -解释 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7。 |
168 | | -``` |
169 | | - |
170 | | -- 示例 2: |
171 | | - |
172 | | -```python |
173 | | -输入:n = 1 |
174 | | -输出:0 |
175 | | -``` |
176 | | - |
177 | | -#### 3.2.3 解题思路 |
178 | | - |
179 | | -这里说下枚举算法的解题思路(注意:提交会超时,只是讲解一下枚举算法的思路)。 |
180 | | - |
181 | | -##### 思路 1:枚举算法(超时) |
182 | | - |
183 | | -对于小于 $n$ 的每一个数 $x,ドル我们可以枚举区间 $[2, x - 1]$ 上的数是否是 $x$ 的因数,即是否存在能被 $x$ 整除的数。如果存在,则该数 $x$ 不是质数。如果不存在,则该数 $x$ 是质数。 |
184 | | - |
185 | | -这样我们就可以通过枚举 $[2, n - 1]$ 上的所有数 $x,ドル并判断 $x$ 是否为质数。 |
186 | | - |
187 | | -在遍历枚举的同时,我们维护一个用于统计小于 $n$ 的质数数量的变量 $cnt$。如果符合要求,则将计数 $cnt$ 加 1ドル$。最终返回该数目作为答案。 |
188 | | - |
189 | | -考虑到如果 $i$ 是 $x$ 的因数,则 $\frac{x}{i}$ 也必然是 $x$ 的因数,则我们只需要检验这两个因数中的较小数即可。而较小数一定会落在 $[2, \sqrt x]$ 上。因此我们在检验 $x$ 是否为质数时,只需要枚举 $[2, \sqrt x]$ 中的所有数即可。 |
190 | | - |
191 | | -利用枚举算法单次检查单个数的时间复杂度为 $O(\sqrt{n}),ドル检查 $n$ 个数的整体时间复杂度为 $O(n \sqrt{n})$。 |
192 | | - |
193 | | -##### 思路 1:代码 |
194 | | - |
195 | | -```python |
196 | | -class Solution: |
197 | | - def isPrime(self, x): |
198 | | - for i in range(2, int(pow(x, 0.5)) + 1): |
199 | | - if x % i == 0: |
200 | | - return False |
201 | | - return True |
202 | | - |
203 | | - def countPrimes(self, n: int) -> int: |
204 | | - cnt = 0 |
205 | | - for x in range(2, n): |
206 | | - if self.isPrime(x): |
207 | | - cnt += 1 |
208 | | - return cnt |
209 | | -``` |
210 | | - |
211 | | -##### 思路 1:复杂度分析 |
212 | | - |
213 | | -- **时间复杂度**:$O(n \times \sqrt{n})$。 |
214 | | -- **空间复杂度**:$O(1)$。 |
215 | | - |
216 | | -### 3.3 统计平方和三元组的数目 |
217 | | - |
218 | | -#### 3.3.1 题目链接 |
219 | | - |
220 | 143 | - [1925. 统计平方和三元组的数目 - 力扣(LeetCode)](https://leetcode.cn/problems/count-square-sum-triples/)
|
221 | 144 |
|
222 | | -#### 3.3.2 题目大意 |
| 145 | +#### 3.2.2 题目大意 |
223 | 146 |
|
224 | 147 | **描述**:给你一个整数 $n$。
|
225 | 148 |
|
@@ -248,30 +171,31 @@ class Solution:
|
248 | 171 | 解释:平方和三元组为 (3,4,5),(4,3,5),(6,8,10) 和 (8,6,10)。
|
249 | 172 | ```
|
250 | 173 |
|
251 | | -#### 3.3.3 解题思路 |
| 174 | +#### 3.2.3 解题思路 |
252 | 175 |
|
253 | 176 | ##### 思路 1:枚举算法
|
254 | 177 |
|
255 | | -我们可以在 $[1, n]$ 区间中枚举整数三元组 $(a, b, c)$ 中的 $a$ 和 $b$。然后判断 $a^2 + b^2$ 是否小于等于 $n,ドル并且是完全平方数。 |
| 178 | +直接枚举 $a$ 和 $b$,计算 $c^2 = a^2 + b^2$,判断 $c$ 是否为整数且 1ドル \leq c \leq n,ドル如果满足条件则计数加一,最后返回总数。 |
256 | 179 |
|
257 | | -在遍历枚举的同时,我们维护一个用于统计平方和三元组数目的变量 $cnt$。如果符合要求,则将计数 $cnt$ 加 1ドル$。最终,我们返回该数目作为答案。 |
| 180 | +该方法时间复杂度为 $O(n^2)$。 |
258 | 181 |
|
259 | | -利用枚举算法统计平方和三元组数目的时间复杂度为 $O(n^2)$。 |
260 | 182 |
|
261 | | -- 注意:在计算中,为了防止浮点数造成的误差,并且两个相邻的完全平方正数之间的距离一定大于 1ドル,ドル所以我们可以用 $\sqrt{a^2 + b^2 + 1}$ 来代替 $\sqrt{a^2 + b^2}$。 |
| 183 | +- 注意:为避免浮点误差,可以用 $\sqrt{a^2 + b^2 + 1}$ 代替 $\sqrt{a^2 + b^2}$,这样判断 $c$ 是否为整数更安全。 |
262 | 184 |
|
263 | 185 | ##### 思路 1:代码
|
264 | 186 |
|
265 | 187 | ```python
|
266 | 188 | class Solution:
|
267 | 189 | def countTriples(self, n: int) -> int:
|
268 | | - cnt = 0 |
269 | | - for a in range(1, n + 1): |
270 | | - for b in range(1, n + 1): |
| 190 | + cnt = 0 # 统计满足条件的三元组个数 |
| 191 | + for a in range(1, n + 1): # 枚举 a |
| 192 | + for b in range(1, n + 1): # 枚举 b |
| 193 | + # 计算 c,注意加 1 防止浮点误差 |
271 | 194 | c = int(sqrt(a * a + b * b + 1))
|
| 195 | + # 判断 c 是否在范围内,且 a^2 + b^2 == c^2 |
272 | 196 | if c <= n and a * a + b * b == c * c:
|
273 | | - cnt += 1 |
274 | | - return cnt |
| 197 | + cnt += 1# 满足条件,计数加一 |
| 198 | + return cnt# 返回最终统计结果 |
275 | 199 | ```
|
276 | 200 |
|
277 | 201 | ##### 思路 1:复杂度分析
|
|
0 commit comments