CC++ & Algorithm

Python枚举算法

中等3
语言版本:C++Python
概述:枚举就像把所有可能的答案一个个试一遍,直到找到正确的那个。

枚举算法:把所有可能都试一遍

想象一下,你有一把密码锁,密码是三位数字(000~999),但你不记得了。最笨但最管用的办法是什么?从000开始,一个个试到999。这就是枚举——把所有可能的情况都列举出来,逐一检查,直到找到答案。

在编程里,枚举也叫暴力破解,是一种非常直接的方法。它不讲究什么巧妙的数学公式,就是靠计算机跑得快,把所有情况都测试一遍。

什么时候用枚举?

生活中的例子

  • 猜数字游戏:对方心里想了一个1~100之间的数,你从1开始每个都猜一遍。虽然慢,但一定猜对。
  • 找零钱:你有1元、2元、5元三种硬币,要凑出10元钱。枚举所有可能的组合(比如1元0个,2元0个,5元2个……),看看哪些方案正好凑出10元。
  • 考试猜答案:10道判断题,每道题可能对或错。把所有2¹⁰=1024种答案组合都试一遍,总有一份是全对的(前提是你有标准答案)。

典型场景

  • 范围比较小(比如小于1000种可能)时,枚举简单又可靠。
  • 你想不出更好的算法时,先用枚举保底。
  • 作为验证其他算法是否正确的“标准答案”。

Python里怎么写枚举?

最常用的工具是for循环。我们用一个例子来看:找出1~50之间所有既能被3整除又能被5整除的数(也就是15的倍数)。

# 枚举1到50的整数,找出同时是3和5倍数的数
for number in range(1, 51):          # number: 当前检查的数字
    if number % 3 == 0 and number % 5 == 0:  # 同时满足两个条件
        print(number, "是3和5的倍数")

运行结果会输出:15、30、45。这就是枚举:把每个数都检查一遍,符合条件就记下来。

枚举的通用步骤

  1. 确定范围:比如从1到100,或者从a到b的所有整数。
  2. 循环遍历:用for循环依次取出范围内的每个值。
  3. 条件判断:用if判断当前值是否符合题目要求。
  4. 记录结果:如果符合,输出或保存起来。

枚举的优缺点

优点缺点
思路简单,容易写当可能情况很多时,会非常慢
保证能找到答案(只要范围完整)需要消耗大量计算资源
适合新手理解“遍历所有可能”不适合大数据或高维问题

比如枚举一个8位数的密码(每位0~9),有10⁸=1亿种可能,普通电脑可能需要几秒钟甚至更久。如果密码是10位,那就是100亿次,现实世界中等不了。

常见错误(新手最容易踩的坑)

1. 范围写错,少了边界

# 错误:想找1~100,却只到99
for i in range(1, 100):  # range(1,100)不包括100

修正:用range(1, 101)或者range(1, 101, 1)

2. 枚举范围不完整,漏掉答案

比如找“100以内质数”时,只检查到int(num**0.5),虽然是对的,但如果忘了加1(比如写成int(num**0.5)而不是int(num**0.5)+1),就可能漏掉完全平方数的情况。例如检查4时,int(4**0.5)=2,如果循环是range(2,2)则根本不会执行,以为4是质数,但实际上4不是质数(能被2整除)。

3. 枚举顺序混乱或重复

比如要找出三个数的所有组合,如果循环写错,可能重复计算相同的组合。例如从{1,2,3}中选两个不同的数,如果写成:

for x in [1,2,3]:
    for y in [1,2,3]:
        if x != y:
            print(x, y)

会输出(1,2)(1,3)(2,1)(2,3)(3,1)(3,2),虽然没重复数,但(1,2)和(2,1)被视为不同,有时我们只想要组合(不考虑顺序)。这时候就要调整循环范围防止重复:例如让第二个循环从第一个的后一位开始。

4. 忘记重置状态

在循环里检查某个条件时,有时候需要先假设“真”,然后在循环内部发现不符合就改成“假”。如果忘记每次循环开始时重置,上一次的结果会影响下一次。下面找质数的例子就需要注意这一点。

完整示例:找水仙花数

水仙花数:一个三位数,每个数位上的数字的立方和等于它本身。比如153 = 1³ + 5³ + 3³。

我们来枚举所有三位数(100~999),逐个检查。

# 找出所有水仙花数(三位数)
print("水仙花数有:")
for num in range(100, 1000):               # num: 当前检查的三位数
    a = num // 100                           # a: 百位数字
    b = (num // 10) % 10                    # b: 十位数字
    c = num % 10                            # c: 个位数字
    if a**3 + b**3 + c**3 == num:          # 立方和等于本身?
        print(num, " = ", a, "^3+", b, "^3+", c, "^3")

运行结果:

水仙花数有:
153  =  1 ^3+ 5 ^3+ 3 ^3
370  =  3 ^3+ 7 ^3+ 0 ^3
371  =  3 ^3+ 7 ^3+ 1 ^3
407  =  4 ^3+ 0 ^3+ 7 ^3

这个例子完整展示了枚举的四步:范围(100~999)、循环、判断条件、输出。

更多练习:百钱百鸡问题

我国古代数学题:公鸡5文一只,母鸡3文一只,小鸡1文三只。用100文钱买100只鸡,问公鸡、母鸡、小鸡各多少只?

我们可以枚举公鸡数量(最多20只),母鸡数量(最多33只),小鸡数量由总数减去前两种得到,但要检查钱数是否匹配。

# 百钱百鸡问题:枚举所有可能的公鸡数和母鸡数
for rooster in range(0, 21):               # rooster: 公鸡数量 (0~20)
    for hen in range(0, 34):               # hen: 母鸡数量 (0~33)
        chick = 100 - rooster - hen        # chick: 小鸡数量,总数为100
        if chick % 3 != 0:                 # 小鸡数量必须是3的倍数,因为3只1文
            continue
        total_money = rooster * 5 + hen * 3 + chick // 3
        if total_money == 100:
            print(f"公鸡{rooster}只,母鸡{hen}只,小鸡{chick}只")

这里用了两层循环枚举公鸡和母鸡,小鸡自动算出,再检查条件。同样是枚举思想。

学完枚举后可以学什么?

  • 剪枝:在枚举过程中提前排除不可能的情况,减少尝试次数(比如百钱百鸡里公鸡数不可能超过20,母鸡不可能超过33)。
  • 回溯:一种更高级的枚举方式,常用于走迷宫、八皇后等问题。
  • 二分查找:当数据有序时,不用枚举,更快。
  • 循环嵌套与列表:很多时候枚举的结果需要存起来,可以用列表存储。

枚举是编程的“基本功”,很多复杂问题的解法里都藏着枚举的影子。当你遇到问题没思路时,不妨先写一个枚举程序,看看结果是什么样的,然后再想办法优化。记住:枚举虽慢,但很可靠

例题精讲

1单选题

以下哪个问题最适合使用枚举算法解决?

A在一个有序数组中查找某个数
B找出1到100之间所有能被7整除的数
C计算两个数的最大公约数(使用辗转相除法)
D排序一个包含10万个数的列表
2判断题

枚举算法的优点是总能找到问题的正确答案,但可能因枚举范围过大而导致执行效率较低。

3填空题
以下程序使用枚举算法找出1到100之间所有既能被3整除又能被5整除的数,请填写正确的条件。
for i in range(1, 101):
    if ___ :
        print(i)
4单选题

关于枚举算法的描述,下列哪一项是正确的?

A枚举算法只能用于整数型的数据
B枚举算法的时间复杂度通常与解空间的大小成正比
C枚举算法必须使用循环嵌套才能实现
D枚举算法保证在所有情况下都能高效运行
5判断题

使用枚举算法求解“鸡兔同笼”问题时,只需枚举鸡的数量即可求出所有可能的解。