何为枚举?
所谓枚举,就是将所有可能的答案去一一列举出来。可能很多人一开始学习算法起步都是通过学习枚举入门的,但枚举这个问题并不是一个简单的问题,相反,我认为枚举是一个比较难的问题。
如何把所有的答案一一列举出来,是第一个需要解决的问题。
那么首先就可以尝试一下下面的例题,两数之和
给定一个整数数组
nums
和一个整数目标值target
,请你在该数组中找出 和为目标值target
的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
其中:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
示例
输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
那么对于上面的这个问题,使用枚举算法来解决,第一步,就是如何把所有的可能结果一一列举出来,这个题是比较简单的,很容易就能想出来思路,因为只需要两个数进行求和,也就是我们先循环枚举第一个数,然后循环枚举第二个数,这样所有的结果都能被找到。
参考代码
class Solution(object):
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
ans=[]
flag=True;
for i in range(len(nums)):
for j in range(i+1,len(nums)):
if nums[i]+nums[j]==target:
ans=[i,j]
flag=False
break
if flag==False:
break
return ans
这里可以把这个题再延申一下,如果不是两个数呢,是未知个数的数,也就是给出一个数组,再给出一个数,这个数组里有多少种组合可以使组合内的数相加,得到那个给出的数?
示例
输入:nums = [1,2,3,4], target = 5
输出:[[0,3],[1,2]]
解释:因为 nums[0] + nums[3] == 5,nums[1] + nums[2] == 5 ,返回 [[0,3],[1,2]] 。
那么这个题比上面的基础版本相比,每一种可能的组合个数不一样了,那么改怎么一一枚举出结果呢?
枚举优化
如果只是单纯的暴力枚举,很显然,这是个毫无含金量的工作。很多情况下,单纯的暴力往往会超时,例如如下的这个题目,计数质数
给定整数
n
,返回 所有小于非负整数n
的质数的数量 。
示例
输入:n = 10 输出:4 解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
那么按照我们上面写的,一一枚举出可能的结果,那么我们很显然会想到如下的代码
class Solution(object):
def countPrimes(self, n):
"""
:type n: int
:rtype: int
"""
num=0
if n<2:
return 0
for i in range(2,n):
flag=True
for j in range(2,i):
if i%j==0:
flag=False
break
if flag:
num+=1
return num
但是这样的提交在力扣上提交会超时
所以,这边就需要对我们枚举的过程进行优化。
埃筛
那么这里,针对质数这一比较特殊的数字,我们可以使用筛法去筛选出是质数的数字。如果用一句话总结,质数的倍数一定不是质数。比如7是质数,那么7的倍数14就不是质数,因为他有因子2和7。
class Solution(object):
def countPrimes(self, n):
"""
:type n: int
:rtype: int
"""
if n<2:
return 0
a=[1]*n
a[0]=a[1]=0
for i in range(2,int(n**0.5)+1):
if a[i]==1:
a[i*i:n:i]=[0]*((n-1-i*i)//i+1)
return sum(a)
上面的程序中,我们定义了一个数组a为筛,1表示是质数,而0表示不是质数,最后只需要统计筛中数值为1的数就可以了。
何时暴力?
但是有的时候,仅仅需要暴力循环便可以通过题目了,那么如何判断我们写的暴力程序能否通过呢?例如下面这个题,统计平方和三元组的数目
一个 平方和三元组
(a,b,c)
指的是满足a2 + b2 = c2
的 整数 三元组a
,b
和c
。给你一个整数
n
,请你返回满足1 <= a, b, c <= n
的 平方和三元组 的数目。输入:n = 5 输出:2 解释:平方和三元组为 (3,4,5) 和 (4,3,5)
1 <= n <= 250
很显然地,我们可以得到如下的暴力程序
class Solution(object):
def countTriples(self, n):
"""
:type n: int
:rtype: int
"""
count=0
for i in range(n-1):
for j in range(i+1,n):
for k in range(j+1,n+1):
if i*i+j*j==k*k:
count+=1
return count*2
因为题目中n是小于250的,所以我们可以看作上面的程序经过了三层循环嵌套,运行了250*250*250次,也就是15625000次,按照时间复杂度换算是O(10^7),而能通过的条件是时间复杂度小于O(10^9),显然是可以通过的。
滑动窗口
待传输文件被切分成多个部分,按照原排列顺序,每部分文件编号均为一个 正整数(至少含有两个文件)。传输要求为:连续文件编号总和为接收方指定数字
target
的所有文件。请返回所有符合该要求的文件传输组合列表。注意,返回时需遵循以下规则:
- 每种组合按照文件编号 升序 排列;
- 不同组合按照第一个文件编号 升序 排列。
输入:target = 12 输出:[[3, 4, 5]] 解释:在上述示例中,存在一个连续正整数序列的和为 12,为 [3, 4, 5]。输入:target = 18 输出:[[3,4,5,6],[5,6,7]] 解释:在上述示例中,存在两个连续正整数序列的和分别为 18,分别为 [3, 4, 5, 6] 和 [5, 6, 7]。
这个题来说,单纯的暴力枚举明显是超出时间复杂度的。这里有一个滑动窗口的相关算法,就是动态地维护一个数组,通过控制左右两端的下标,来计算当前的维护的数组之和是否与目标相同。
class Solution(object):
def fileCombination(self, target):
"""
:type target: int
:rtype: List[List[int]]
"""
ans=[]
i,j=1,2
while j<=target//2+1:
cur_sum=sum(list(range(i,j+1)))
if cur_sum<target:
j+=1
elif cur_sum>target:
i+=1
else:
ans.append(list(range(i,j+1)))
j+=1
return ans
给你两个正整数
a
和b
,返回a
和b
的 公 因子的数目。如果
x
可以同时整除a
和b
,则认为x
是a
和b
的一个 公因子 。输入:a = 12, b = 6 输出:4 解释:12 和 6 的公因子是 1、2、3、6 。
1 <= a, b <= 1000
对于这个题目来说,我们可以缩减需要枚举的数目,最简单的缩减就是去到两个数之间较小的那一个,但是,在这个基础上能否再次缩减呢?很显然,需要找到两个数之间最大的那个可能是公因子的数,很显然就是这个数的二分之一。
class Solution(object):
def fileCombination(self, target):
"""
:type target: int
:rtype: List[List[int]]
"""
ans=[]
i,j=1,2
while j<=target//2+1:
cur_sum=sum(list(range(i,j+1)))
if cur_sum<target:
j+=1
elif cur_sum>target:
i+=1
else:
ans.append(list(range(i,j+1)))
j+=1
return ans
这个题主要就是枚举出哪些点在圆内。
然后看到了一个比较有意思的程序,把整段的python程序最后融合成了一句pyhton程序。
class Solution(object):
def countLatticePoints(self, circles):
"""
:type circles: List[List[int]]
:rtype: int
"""
return sum(any((a-i)**2+(b-j)**2 <= c**2 for a, b, c in circles)\
for i in range(max(i+k for i, j, k in circles)+1)\
for j in range(max(j+k for i, j, k in circles)+1))