枚举算法(LeetCode)

1. 枚举算法简介

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

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

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

  1. 多数情况下容易编程实现,也容易调试。
  2. 建立在考察大量状态、甚至是穷举所有状态的基础上,所以算法的正确性比较容易证明。

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

2. 枚举算法的解题思路

2.1 枚举算法的解题思路

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

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

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

  1. 确定枚举对象、枚举范围和判断条件,并判断条件设立的正确性。
  2. 一一枚举可能的情况,并验证是否是问题的解。
  3. 考虑提高枚举算法的效率。

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

  1. 抓住问题状态的本质,尽可能缩小问题状态空间的大小。
  2. 加强约束条件,缩小枚举范围。
  3. 根据某些问题特有的性质,例如对称性等,避免对本质相同的状态重复求解。

2.2 枚举算法的简单应用

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

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

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

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

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

    1. 确定枚举对象:枚举对象为公鸡、母鸡、小鸡的只数,那么我们可以用变量 𝑥、$y$、$z$ 分别来代表公鸡、母鸡、小鸡的只数。
    2. 确定枚举范围:因为总共买了 100 只鸡,所以 0≤𝑥,𝑦,𝑧≤100,则 𝑥、$y$、$z$ 的枚举范围为 [0,100]。
    3. 确定判断条件:根据题意,我们可以列出两个方程式:$5 \times x + 3 \times y + \frac{z}{3} = 100$,$x + y + z = 100$。在枚举 𝑥、$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. 在上面的代码中,我们枚举了 𝑥、$y$、$z$,但其实根据方程式 𝑥+𝑦+𝑧=100,得知:$z$ 可以通过 𝑧=100−𝑥−𝑦 而得到,这样我们就不用再枚举 𝑧 了。
    2. 在上面的代码中,对 𝑥、$y$ 的枚举范围是 [0,100],但其实如果所有钱用来买公鸡,最多只能买 20 只,同理,全用来买母鸡,最多只能买 33 只。所以对 𝑥 的枚举范围可改为 [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 题目大意

描述:给定一个整数数组 𝑛𝑢𝑚𝑠 和一个整数目标值 𝑡𝑎𝑟𝑔𝑒𝑡。

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

说明

  • 2≤𝑛𝑢𝑚𝑠.𝑙𝑒𝑛𝑔𝑡ℎ≤104。
  • −109≤𝑛𝑢𝑚𝑠[𝑖]≤109。
  • −109≤𝑡𝑎𝑟𝑔𝑒𝑡≤109。
  • 只会存在一个有效答案。

示例

  • 示例 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]

 双指针解法

from typing import List

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        list = [ [ 0 for _ in range(2)] for _ in range(len(nums))]
        for i in range(len(nums)):
            list[i][0] = i
            list[i][1] = nums[i]
        list.sort(key = lambda x:x[1])
        i,j=0,len(nums)-1
        while i<j:
            sum = list[i][1]+list[j][1]
            if sum == target:
                return [list[i][0],list[j][0]]
            elif sum > target:
                j -= 1
            else:
                i += 1
        return []

if __name__ == '__main__':
    s = Solution()
    print(s.twoSum([2,7,11,15],9))

 Hash解法

from typing import List

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        map={}
        for index,value in enumerate(nums):
            x=target - value
            if x in map:
                return [map[x],index]
            map[value]=index
        return []

if __name__ == '__main__':
    s = Solution()
    print(s.twoSum([2,7,11,15],9))

3.2 计数质数 

3.2.1 题目链接
3.2.2 题目大意

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

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

说明

  • 0≤𝑛≤5×106。

示例

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

埃拉托斯特尼筛法

import math
from typing import List


class Solution:
    def sum(self, flag: List[bool]) -> int:
        return sum(flag)

    def countPrimes(self, n: int) -> int:
        if n < 2:
            return 0
        flags = [True for i in range(n)]
        flags[0], flags[1] = False, False
        for i in range(2, int(math.sqrt(n)+1)):
            if flags[i]:
                for j in range(i*i,n,i):
                    flags[j] = False
        return self.sum(flags)


if __name__ == '__main__':
    s = Solution()
    print(s.countPrimes(10))

 

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

3.3.1 题目链接
3.3.2 题目大意

描述:给你一个整数 𝑛。

要求:请你返回满足 1≤𝑎,𝑏,𝑐≤𝑛 的平方和三元组的数目。

说明

  • 平方和三元组:指的是满足 𝑎2+𝑏2=𝑐2 的整数三元组 (𝑎,𝑏,𝑐)。
  • 1≤𝑛≤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)。

双指针解法

class Solution:
    def countTriples(self, n: int) -> int:
        ans = set()
        for c in range(1, n + 1):
            i, j = 1, c - 1
            while i < j:
                s = i * i + j * j
                if s > c * c:
                    j -= 1
                elif s < c * c:
                    i += 1
                else:
                    ans.add((i, j, c))
                    ans.add((j, i, c))
                    i += 1
        return len(ans)

if __name__ == '__main__':
    s = Solution()
    print(s.countTriples(5))

 4.枚举算法总结

枚举算法虽然简单暴力,容易理解,但是绝大多数情况下对计算机的开销较大

可以采用下面几种方法进行优化

  1. 剪枝:在搜索过程中提前排除不可能达到目标的部分,减少搜索空间。

  2. 排序与贪心策略:先对数据进行排序,然后使用贪心策略来快速缩小搜索范围。

  3. 位运算:利用位运算代替部分循环操作,提高效率。

  4. 动态规划:对于可以分解成子问题的问题,可以使用动态规划来优化枚举过程。

  5. 并行计算:利用多线程或多进程技术来同时处理不同的分支。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值