【基础算法实战】
【1】枚举算法
文章目录
前言
枚举算法是一种最基本但却非常重要的算法思想。在解决复杂问题时,我们常常会从最简单的枚举开始,通过全面地考虑所有可能的情况,找到问题的解。在本文中,我将结合自己的理解,深入探讨枚举算法的原理、应用场景以及如何优化枚举算法以提高效率。
一、枚举算法简介
枚举算法(Enumeration Algorithm),也称为穷举算法,是一种按照问题本身的性质,逐一列举出所有可能的解,并通过判断条件筛选出满足要求的解的方法。在这个过程中,我们需要确保:
- 不遗漏任何可能的解;
- 不重复计算相同的状态。
1.1 枚举算法的核心思想
枚举算法的核心在于遍历所有可能的状态空间。对于一个给定的问题,我们首先需要明确:
- 枚举对象:要枚举的变量或元素;
- 枚举范围:枚举对象可能的取值范围;
- 判断条件:用于验证当前枚举状态是否满足问题的要求。
尽管枚举算法在问题规模较大时效率较低,但它有以下优点: - 简单易懂:实现过程直观,便于理解和编码;
- 正确性高:由于遍历了所有可能的情况,算法的正确性容易验证。
因此,枚举算法常用于: - 解决小规模问题;
- 作为复杂算法的子模块,为主算法提供必要的信息。
二、枚举算法的解题思路
2.1 一般步骤
在使用枚举算法解决问题时,可以按照以下步骤进行:
- 确定枚举对象和范围:
- 找出问题中需要枚举的变量;
- 确定这些变量的取值范围,尽可能缩小范围以提高效率。
- 设定判断条件:
-根据问题要求,制定判断当前状态是否满足条件的规则。 - 遍历所有可能的组合:
- 使用循环结构,遍历枚举对象的所有可能取值组合。
- 验证并记录解:
- 在每次迭代中,使用判断条件验证当前状态;
- 如果满足条件,则记录或输出解。
- 优化算法(可选):
- 通过剪枝、减少重复计算等方法,提高枚举算法的效率。
2.2 提高枚举算法效率的方法
虽然枚举算法本身简单,但在大多数情况下,我们需要对其进行优化以提高效率。以下是一些常用的优化策略:
- 剪枝:
- 在枚举过程中,如果发现某个分支不可能产生有效解,立即跳过,避免不必要的计算。
- 缩小搜索空间:
- 利用问题的约束条件,减少需要遍历的取值范围。
- 利用对称性和规律:
- 如果问题存在对称性或某些规律,可以避免重复计算等价的状态。
- 预处理:例如,提前计算并存储一些常用的结果,以减少计算量。
三、枚举算法的实际应用
为了更好地理解枚举算法,我们通过几个经典的例子来说明其应用。
3.1 百钱买百鸡问题
问题描述:
公鸡 5 元一只,母鸡 3 元一只,小鸡 1 元三只。用 100 元买 100 只鸡,问公鸡、母鸡和小鸡各多少只?
解题思路:
- 确定枚举对象和范围:
- 枚举对象:公鸡数( x x x)、母鸡数( y y y)、小鸡数( z z z)。
- 枚举范围:
- x ∈ [ 0 , 20 ] x \in [0,20] x∈[0,20],因为最多只能买 20 只公鸡( 5 × 20 = 100 5 \times 20 = 100 5×20=100)。
- y ∈ [ 0 , 33 ] y \in [0,33] y∈[0,33],因为最多只能买 33 只母鸡( 3 × 33 = 99 3 \times 33 = 99 3×33=99)。
- z = 100 − x − y z = 100 - x - y z=100−x−y,由总数量 100 只得出。
- 设定判断条件:
- 价格条件: 5 x + 3 y + z 3 = 100 5x + 3y + \dfrac{z}{3} = 100 5x+3y+3z=100。
- 数量条件: z z z 必须是 3 的倍数(因为小鸡 3 只 1 元)。
- 遍历并验证:
class Solution:
def buyChicken(self):
for x in range(21):
for y in range(34):
z = 100 - x - y
if z % 3 == 0 and 5 * x + 3 * y + z // 3 == 100:
print(f"公鸡 {x} 只,母鸡 {y} 只,小鸡 {z} 只")
运行结果:
公鸡 0 只,母鸡 25 只,小鸡 75 只
公鸡 4 只,母鸡 18 只,小鸡 78 只
公鸡 8 只,母鸡 11 只,小鸡 81 只
公鸡 12 只,母鸡 4 只,小鸡 84 只
个人理解:
在这个问题中,通过合理地缩小枚举范围,我们大大减少了计算量。注意到小鸡的价格和数量存在特殊关系(3 只 1 元),所以在计算时需要确保
z
z
z 是 3 的倍数,这就是一种剪枝的应用。
3.2 两数之和
问题描述:
给定一个整数数组 nums 和一个目标值 target,请在数组中找出和为目标值的两个整数,返回它们的下标。
示例:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解题思路:
- 直接枚举:
- 使用两重循环,遍历所有可能的数对,检查它们的和是否等于目标值。
- 优化思路:
- 哈希表:为了提高效率,可以使用哈希表存储已经遍历过的数及其下标。在遍历数组时,检查target - nums[i] 是否在哈希表中,如果存在,则找到了答案。
代码实现(枚举法):
class Solution:
def twoSum(self, nums, target):
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
return []
个人理解:
虽然直接枚举可以解决问题,但时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),对于大规模数据不够高效。通过引入哈希表,时间复杂度可以降低到
O
(
n
)
O(n)
O(n),体现出枚举结合其他数据结构进行优化的思想。
class Solution:
def twoSum(self, nums, target):
hash_table = {}
for i, num in enumerate(nums):
complement = target - num
if complement in hash_table:
return [hash_table[complement], i]
hash_table[num] = i
return []
3.3 计数质数
问题描述:
统计所有小于非负整数 n 的质数的数量。
示例:
输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个,它们是 2、3、5、7。
解题思路:
- 直接枚举:
- 对于每个小于 n 的数,判断其是否为质数。
- 优化思路:
- 埃拉托色尼筛法:创建一个布尔数组表示数的状态,从小到大遍历,将每个质数的倍数标记为非质数。
代码实现(枚举法):
class Solution:
def countPrimes(self, n):
count = 0
for num in range(2, n):
is_prime = True
for i in range(2, int(num ** 0.5)):
if num % i == 0:
is_prime = False
break
if is_prime:
count += 1
return count
个人理解:
直接枚举判断质数的时间复杂度较高。在实际应用中,使用埃拉托色尼筛法可以大大提高效率。这也是一个从枚举到优化的典型过程。
class Solution:
def countPrimes(self, n):
if n < 2:
return 0
is_prime = [True] * n
is_prime[0], is_prime[1] = False, False
for i in range(2, int(n ** 0.5) + 1):
if is_prime[i]:
is_prime[i*i:n:i] = [False] * len(range(i*i, n, i))
return sum(is_prime)
3.4 统计平方和三元组的数目
问题描述:
给定一个整数 n n n ,返回满足 1 ≤ a , b , c ≤ n 1 ≤ a, b, c ≤ n 1≤a,b,c≤n 且 a 2 + b 2 = c 2 a^2 + b^2 = c^2 a2+b2=c2 的整数三元组的数目。
示例:
输入:n = 5
输出:2
解释:平方和三元组为 (3,4,5) 和 (4,3,5)。
解题思路:
- 直接枚举:
- 枚举所有可能的 ( a , b ) (a, b) (a,b) 组合,计算 c = s q r t ( a 2 + b 2 ) c = sqrt(a^2 + b^2) c=sqrt(a2+b2),判断 c c c 是否为整数且不超过 n n n。
- 优化思路:
- 利用数学性质:注意到 a a a 和 b b b 的顺序性,可以减少重复计算。
- 预计算:将所有可能的 c 2 c^2 c2 存储起来,查找时效率更高。
代码实现(枚举法):
class Solution:
def countTriples(self, n):
count = 0
for a in range(1, n + 1):
for b in range(1, n + 1):
c_square = a * a + b * b
c = int(c_square ** 0.5)
if c <= n and c * c == c_square:
count += 1
return count
个人理解:
在这个问题中,通过预先计算并存储
1
1
1 到
n
n
n 的平方值,可以加速查找过程。此外,考虑到
(
a
,
b
,
c
)
(a, b, c)
(a,b,c) 和
(
b
,
a
,
c
)
(b, a, c)
(b,a,c) 是不同的三元组,所以需要遍历所有的
(
a
,
b
)
(a, b)
(a,b) 组合。如果问题要求不计顺序,可以进一步优化。
class Solution:
def countTriples(self, n):
squares = {i * i for i in range(1, n + 1)}
count = 0
for a in range(1, n + 1):
for b in range(1, n + 1):
if a * a + b * b in squares:
count += 1
return count
四、优化枚举算法的实践技巧
4.1 剪枝策略
示例:在百钱买百鸡问题中,我们知道小鸡的数量必须是 3 的倍数,所以在枚举时可以直接跳过不符合条件的情况。
实践技巧:
- 在循环中加入条件判断,提前结束不可能成功的分支;
- 利用问题的上下界,动态调整枚举范围。
4.2 利用数学规律
示例:在统计平方和三元组的数目时,我们可以利用毕达哥拉斯定理,只需要枚举
a
a
a 和
b
b
b,计算
c
c
c 是否为整数。
实践技巧:
- 掌握相关的数学定理和公式,可以大大简化问题;
- 寻找问题中的对称性和重复性,避免冗余计算。
4.3 结合数据结构
示例:在两数之和问题中,使用哈希表可以将查找时间从
O
(
n
)
O(n)
O(n) 降低到
O
(
1
)
O(1)
O(1)。
实践技巧:
- 根据问题特点,选择合适的数据结构(如哈希表、堆、栈等);
- 在枚举的基础上,利用数据结构提高查找和存储效率。
总结
枚举算法虽然简单,但在算法设计中占有重要地位。通过枚举,我们可以全面地考虑问题的所有可能性,确保解的正确性。在实际应用中,我们需要根据具体问题,结合优化策略,提高算法的效率。
关键点回顾:
- 明确枚举对象和范围:这是枚举算法的基础,直接影响算法的正确性和效率。
- 设定合理的判断条件:确保筛选出的解满足问题要求。
- 优化算法:通过剪枝、利用数学性质和数据结构,提升算法性能。
- 实践经验:多练习经典问题,积累经验,培养发现优化点的敏锐度。
枚举算法不仅是一种算法,更是一种思维方式。在解决复杂问题时,我们往往会被问题的复杂性所迷惑,但回归本质,从最简单的枚举开始,往往能找到突破口。与此同时,我们也要有优化的意识,不断思考如何在保证正确性的前提下,提高算法的效率。
1800

被折叠的 条评论
为什么被折叠?



