枚举(英语:Enumerate)是基于已有知识来猜测答案的一种问题求解策略。
枚举的思想是不断地猜测,从可能的集合中一个一个尝试,然后再判断题目的条件是否成立。
对于一个问题,使用枚举算法首先是找到解可能存在的范围或者求解需要考虑的范围,然后在这个范围中,将元素一个一个与题目要求进行比较,找到符合要求的答案。
比如:找出给定非0整数序列中的最大值,我们可以确定范围就是这个序列的全部元素,然后遍历这个序列一个一个进行比较,记录当前最大值,遍历新的元素时都与当前最大值比较,如果更大就替换这个最大值。当我们遍历完全部可能后,便能得到解。
当然,枚举并不是一定要像穷举遍历全部情况,枚举的核心在于灵活筛选出所有的可能情况,然后一个一个尝试。
枚举的时候要想清楚可能的情况是什么?要枚举哪些要素?找到解可能存在的范围或者求解需要考虑的范围。
在用枚举法解决问题的时候,一定要想清楚这两件事:枚举的范围是什么?是所有的内容都需要枚举吗?我们可以通过构建更加巧妙的枚举方法来减少枚举空间,在更小的范围中求解。同时也要审视枚举空间是否合理,减少冗杂多余的空间,可能只在该枚举空间部分空间下就足够求解。否则会带来不必要的时间开销。
根据题目判断。比如要求的是最大的符合条件的素数,那自然是从大到小枚举比较合适。
以下是一个使用枚举解题与优化枚举范围的例子。
题目:给定一个数组,数组中的数互不相同,求其中和为 0的数对的个数。
示例
输入:[3, -1, 2, 1, -3, 4]
输出:2
说明:3和-3,-1和1和都为0,故2对
对于这个问题我们可以得到很简单的求解方法,对于每个元素都和所有元素依次比较,判断和是否为0,和为0时计算+1。由于遍历时(1,-1)和(-1,1)和都为0,即反过来也会算一对,所有答案要除以2.
求解代码1:
# Python
# a为输入数组
a = list(map(int, input().split()))
n = len(a)
ans = 0
for i in range(n):
for j in range(n):
if a[i] + a[j] == 0:
ans += 1
ans = ans // 2
print(ans)
在上面的代码中,我们发现满足要求的一对数会重复出现两次,对于满足要求的一对数,在数组中是有先后顺序的,按此顺序我们可以不出现重复,即我们对第一层循环遍历的元素,不再仅仅认为是一对数中的一个数,而是认为是满足要求的一对数中后面的数,然后再去找是否存在满足要求的前面的数,只需要遍历到该数,不需要遍历后面的数。例如[-4, -1, 2, 1, 3],中[-1, 1]满足要求。对于1,我们只需要遍历[-4, -1, 2]即可。
求解代码2:
# Python
# a为输入数组
a = list(map(int, input().split()))
n = len(a)
ans = 0
for i in range(n):
for j in range(i):
if a[i] + a[j] == 0:
ans += 1
print(ans)
两个数是否都一定要枚举出来呢?枚举其中一个数之后,题目的条件已经确定了其他的要素(另一个数)的条件,如果能找到一种方法直接判断题目要求的那个数是否存在,就可以省掉枚举后一个数的时间了。我们可以用一个“桶”来记录遍历过的数,当遍历到可以配对的另外一个数时,计数即可。(假设绝对值最大为3,则桶的大小为7,0到6表示-3到3共7个数,如果先遇到3则桶的位置6被标记,再遇到-3时会判断6被标记,计数+1)
求解代码3
# Python
a = list(map(int, input().split()))
n = len(a)
ans = 0
maxa = max(max(a), abs(min(a)))
met = [False] * (maxa * 2 + 1)
for i in range(n):
if met[maxa - a[i]]:
ans += 1
met[a[i] + maxa] = True
print(ans)
至此算法时间复杂度O(n),只需要一次遍历即可求解答案
所有我们可以看到,枚举算法虽然将可能的情况一一列举然后求解,但并不一定就是时间复杂度很高的算法,选取合适的枚举策略,可以大大提高效率。