算法数据结构——枚举算法(Enumeration Algorithm)讲解,如何简单快速的解决问题

1. 枚举算法简介

枚举算法(Enumeration Algorithm):也称为穷举算法,指的是按照问题本身的性质,一一列举出该问题所有可能的解,并在逐一列举的过程中,将它们逐一与目标状态进行比较以得出满足问题要求的解。在列举的过程中,既不能遗漏也不能重复。

枚举算法的核心思想是:通过列举问题的所有状态,将它们逐一与目标状态进行比较,从而得到满足条件的解。

由于枚举算法要通过列举问题的所有状态来得到满足条件的解,因此,在问题规模变大时,其效率一般是比较低的。但是枚举算法也有自己特有的优点:

  1. 多数情况下容易编程实现,也容易调试。

  2. 建立在考察大量状态、甚至是穷举所有状态的基础上,所以算法的正确性比较容易证明。

所以,枚举算法通常用于求解问题规模比较小的问题,或者作为求解问题的一个子算法出现,通过枚举一些信息并进行保存,而这些消息的有无对主算法效率的高低有着较大影响。

2. 枚举算法的解题思路

2.1 枚举算法的解题思路

枚举算法是设计最简单、最基本的搜索算法。是我们在遇到问题时,最应该优先考虑的算法。

因为其实现足够简单,所以在遇到问题时,我们往往可以先通过枚举算法尝试解决问题,然后在此基础上,再去考虑其他优化方法和解题思路。

采用枚举算法解题的一般思路如下:

  1. 确定枚举对象、枚举范围和判断条件,并判断条件设立的正确性。

  2. 一一枚举可能的情况,并验证是否是问题的解。

  3. 考虑提高枚举算法的效率。

我们可以从下面几个方面考虑提高算法的效率:

  1. 抓住问题状态的本质,尽可能缩小问题状态空间的大小。

  2. 加强约束条件,缩小枚举范围。

  3. 根据某些问题特有的性质,例如对称性等,避免对本质相同的状态重复求解。

2.2 枚举算法的简单应用

下面举个著名的例子:「百钱买百鸡问题」。这个问题是我国古代数学家张丘在「算经」一书中提出的。该问题叙述如下:

百钱买百鸡问题:鸡翁一,值钱五;鸡母一,值钱三;鸡雏三,值钱一;百钱买百鸡,则鸡翁、鸡母、鸡雏各几何?

翻译一下,意思就是:公鸡一只五块钱,母鸡一只三块钱,小鸡三只一块钱。现在我们用 $100$ 块钱买了 $100$ 只鸡,问公鸡、母鸡、小鸡各买了多少只?

下面我们根据算法的一般思路来解决一下这道题。

  1. 确定枚举对象、枚举范围和判断条件,并判断条件设立的正确性。

    1. 确定枚举对象:枚举对象为公鸡、母鸡、小鸡的只数,那么我们可以用变量 $x$、$y$、$z$ 分别来代表公鸡、母鸡、小鸡的只数。

    2. 确定枚举范围:因为总共买了 $100$ 只鸡,所以 $0 \le x, y, z \le 100$,则 $x$、$y$、$z$ 的枚举范围为 $[0, 100]$。

    3. 确定判断条件:根据题意,我们可以列出两个方程式:$5 \times x + 3 \times y + \frac{z}{3} = 100$,$x + y + z = 100$。在枚举 $x$、$y$、$z$ 的过程中,我们可以根据这两个方程式来判断是否当前状态是否满足题意。

  2. 一一枚举可能的情况,并验证是否是问题的解。

    1. 根据枚举对象、枚举范围和判断条件,我们可以顺利写出对应的代码。

      class Solution:
          def buyChicken(self):
              for x in range(101):
                  for y in range(101):
                      for z in range(101):
                          if z % 3 == 0 and 5 * x + 3 * y + z // 3 == 100 and x + y + z == 100:
                              print("公鸡 %s 只,母鸡 %s 只,小鸡 %s 只" % (x, y, z))

  3. 考虑提高枚举算法的效率。

    1. 在上面的代码中,我们枚举了 $x$、$y$、$z$,但其实根据方程式 $x + y + z = 100$,得知:$z$ 可以通过 $z = 100 - x - y$ 而得到,这样我们就不用再枚举 $z$ 了。

    2. 在上面的代码中,对 $x$、$y$ 的枚举范围是 $[0, 100]$,但其实如果所有钱用来买公鸡,最多只能买 $20$ 只,同理,全用来买母鸡,最多只能买 $33$ 只。所以对 $x$ 的枚举范围可改为 $[0, 20]$,$y$ 的枚举范围可改为 $[0, 33]$。

    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("公鸡 %s 只,母鸡 %s 只,小鸡 %s 只" % (x, y, z))

3. 枚举算法的应用

3.1 两数之和

3.1.1 题目链接

3.1.2 题目大意

描述:给定一个整数数组 nums 和一个整数目标值 target

要求:在该数组中找出和为 target 的两个整数,并输出这两个整数的下标。可以按任意顺序返回答案。

说明

  • $2 \le nums.length \le 10^4$。

  • $-10^9 \le nums[i] \le 10^9$。

  • $-10^9 \le target \le 10^9$。

  • 只会存在一个有效答案。

示例

  • 示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
  • 示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

3.1.3 解题思路

这里说下枚举算法的解题思路。

思路 1:枚举算法

  1. 使用两重循环枚举数组中每一个数 nums[i]nums[j],判断所有的 nums[i] + nums[j] 是否等于 target

  2. 如果出现 nums[i] + nums[j] == target,则说明数组中存在和为 target 的两个整数,将两个整数的下标 ij 输出即可。

思路 1:代码

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        for i in range(len(nums)):
            for j in range(i + 1, len(nums)):
                if i != j and nums[i] + nums[j] == target:
                    return [i, j]
        return []

思路 1:复杂度分析

  • 时间复杂度:$O(n^2)$

  • 空间复杂度:$O(1)$。

3.2 计数质数

3.2.1 题目链接

3.2.2 题目大意

描述:给定 一个非负整数 $n$。

要求:统计小于 $n$ 的质数数量。

说明

  • $0 \le n \le 5 * 10^6$。

示例

  • 示例 1:

输入 n = 10
输出 4
解释 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7。
  • 示例 2:

输入:n = 1
输出:0

3.2.3 解题思路

这里说下枚举算法的解题思路(注意:提交会超时,只是讲解一下枚举算法的思路)。

思路 1:枚举算法(超时)

对于小于 $n$ 的每一个数 $x$,我们可以枚举区间 $[2, x - 1]$ 上的数是否是 $x$ 的因数,即是否存在能被 $x$ 整数的数。如果存在,则该数 $x$ 不是质数。如果不存在,则该数 $x$ 是质数。

这样我们就可以通过枚举 $[2, n - 1]$ 上的所有数 $x$,并判断 $x$ 是否为质数。

在遍历枚举的同时,我们维护一个用于统计小于 $n$ 的质数数量的变量 cnt。如果符合要求,则将计数 cnt 加 $1$。最终返回该数目作为答案。

考虑到如果 $i$ 是 $x$ 的因数,则 $\frac{x}{i}$ 也必然是 $x$ 的因数,则我们只需要检验这两个因数中的较小数即可。而较小数一定会落在 $[2, \sqrt x]$ 上。因此我们在检验 $x$ 是否为质数时,只需要枚举 $[2, \sqrt x]$ 中的所有数即可。

利用枚举算法单次检查单个数的时间复杂度为 $O(\sqrt{n})$,检查 $n$ 个数的整体时间复杂度为 $O(n \sqrt{n})$。

思路 1:代码

class Solution:
    def isPrime(self, x):
        for i in range(2, int(pow(x, 0.5)) + 1):
            if x % i == 0:
                return False
        return True
​
    def countPrimes(self, n: int) -> int:
        cnt = 0
        for x in range(2, n):
            if self.isPrime(x):
                cnt += 1
        return cnt

思路 1:复杂度分析

  • 时间复杂度:$O(n \times \sqrt{n})$。

  • 空间复杂度:$O(1)$。

3.3 统计平方和三元组的数目

3.3.1 题目链接

3.3.2 题目大意

描述:给你一个整数 $n$。

要求:请你返回满足 $1 \le a, b, c \le n$ 的平方和三元组的数目。

说明

  • 平方和三元组:指的是满足 $a^2 + b^2 = c^2$ 的整数三元组 $(a, b, c)$。

  • $1 \le n \le 250$。

示例

  • 示例 1:

输入 n = 5
输出 2
解释 平方和三元组为 (3,4,5) 和 (4,3,5)。
  • 示例 2:

输入:n = 10
输出:4
解释:平方和三元组为 (3,4,5),(4,3,5),(6,8,10) 和 (8,6,10)。

3.3.3 解题思路

思路 1:枚举算法

我们可以在 $[1, n]$ 区间中枚举整数三元组 $(a, b, c)$ 中的 $a$ 和 $b$。然后判断 $a^2 + b^2$ 是否小于等于 $n$,并且是完全平方数。

在遍历枚举的同时,我们维护一个用于统计平方和三元组数目的变量 cnt。如果符合要求,则将计数 cnt 加 $1$。最终,我们返回该数目作为答案。

利用枚举算法统计平方和三元组数目的时间复杂度为 $O(n^2)$。

  • 注意:在计算中,为了防止浮点数造成的误差,并且两个相邻的完全平方正数之间的距离一定大于 $1$,所以我们可以用 $\sqrt{a^2 + b^2 + 1}$ 来代替 $\sqrt{a^2 + b^2}$。

思路 1:代码

class Solution:
    def countTriples(self, n: int) -> int:
        cnt = 0
        for a in range(1, n + 1):
            for b in range(1, n + 1):
                c = int(sqrt(a * a + b * b + 1))
                if c <= n and a * a + b * b == c * c:
                    cnt += 1
        return cnt

思路 1:复杂度分析

  • 时间复杂度:$O(n^2)$。

  • 空间复杂度:$O(1)$。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在Java算法中,枚举法(Enumeration)是一种常见的算法技术,它通常用于在有限的选择集合中查找或比较特定的值。 具体地说,枚举法是通过依次遍历选择集合中的每个元素,并对其进行处理或比较,以确定所需的值是否存在或符合要求。在Java中,枚举法通常可以通过以下步骤实现: 1. 定义一个包含所有可选值的枚举类型(Enum)。 2. 遍历枚举类型中的所有值,对每个值进行处理或比较。 3. 根据具体的需求,可能需要在遍历过程中保存一些状态或结果。 以下是一个使用枚举法查找数组中最小值的Java代码示例: ```java public class EnumExample { enum Option { MIN, MAX } public static int findExtremeValue(int[] array, Option option) { int extremeValue = array[0]; for (int i = 1; i < array.length; i++) { if (option == Option.MIN) { if (array[i] < extremeValue) { extremeValue = array[i]; } } else if (option == Option.MAX) { if (array[i] > extremeValue) { extremeValue = array[i]; } } } return extremeValue; } public static void main(String[] args) { int[] array = { 3, 5, 1, 7, 9 }; int minValue = findExtremeValue(array, Option.MIN); int maxValue = findExtremeValue(array, Option.MAX); System.out.println("Min value: " + minValue); System.out.println("Max value: " + maxValue); } } ``` 在这个示例中,我们定义了一个包含两个可选值(最小值和最大值)的枚举类型Option,并使用findExtremeValue方法遍历了数组中的所有元素,以查找最小值或最大值。具体地说,我们使用了if语句来根据选项参数option的值来决定如何比较数组中的元素,并在遍历结束后返回极值。 ### 回答2: 枚举法是一种求解问题的方法,它通过遍历所有可能的解,逐个进行尝试和比较,从中找到满足条件的最佳解。 在Java算法中,枚举法经常被用于求解搜索、排序、组合、排列和子集等问题。它的基本思路是通过循环遍历所有可能的组合或排列,逐个进行判断和处理。 举个例子,假设我们要在一个整数数组中找到两个数之和为给定值的组合。可以通过枚举法来解决。首先,我们可以使用双重循环遍历数组中的每一对数,然后判断它们的和是否等于给定值。如果相等,则找到了一个满足条件的组合。 另外一个例子是求解最短路径问题。通过枚举法,可以遍历所有可能的路径,然后计算每条路径的长度,最后找到最短的路径。 在实际应用中,枚举法的时间复杂度通常较高,因为需要遍历所有可能的解空间。为了提高效率,可以结合其他算法进行优化,如剪枝、动态规划等。 总而言之,枚举法是一种常用的求解问题的方法,它通过遍历所有可能的解,逐个进行尝试和比较,从中找到满足条件的最佳解。在Java算法中,它经常被用于求解搜索、排序、组合、排列和子集等问题。 ### 回答3: 枚举法是一种常用的算法思想,用于解决问题中所有可能的情况。在Java算法中,枚举法可以通过以下步骤运用: 1.确定问题的解空间:首先,要确定问题的可能解的范围,即问题的解空间。解空间可以是一个集合、一个数组、一个字符串等。 2.生成可能的解:根据问题的解空间,生成所有可能的解。通常可以使用循环结构来枚举解空间中的每个元素或组合。如果解空间较大,可以使用递归方式进行枚举。 3.检验可能的解:对于生成的每个可能的解,进行检验,判断其是否满足问题的约束条件。如果满足约束条件,则将其作为候选解,否则舍弃。 4.求解最优解:根据问题的具体要求,对所有满足约束条件的候选解进行比较,找到最优解或符合要求的解。可以使用比较运算符、排序算法等方式进行比较。 5.输出结果:输出求解得到的最优解或符合要求的解。 需要注意的是,枚举法在解空间较大的情况下往往会造成时间复杂度较高,因此在使用时需要合理选择解空间,避免不必要的枚举。此外,可以通过优化算法设计、剪枝等方式提高枚举算法的效率。 总而言之,枚举法是一种通过穷举所有可能解来求解问题的算法思想,在Java中可以通过循环或递归方式进行枚举,并对每个可能解进行检验和比较,最终求解出最优解或符合要求的解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白话机器学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值