前言:
算法,简单理解就是“解题思路”,要找到解题的方法和关键的运算条件语句
本文基于《Python算法从入门到实践》薛小龙老师编著的这本书,佐以笔者的思考而成,主要内容为时空复杂度的简单介绍,枚举算法的详细阐述与应用
一、算法需要的一点基础:时间复杂度和空间复杂度
1.时间复杂度
O(n),n代表复杂度是n倍的级别(还有别的),复杂度越大,代码最坏情况下(就是数据量特别大的时候)跑的时间就越久,效率越低
下面是一篇大佬的总结,我就抛砖引玉了
详解时间复杂度计算公式(附例题细致讲解过程)_时间复杂度怎么算-CSDN博客
2.空间复杂度
代码跑起来所占临时空间的大小,经常牺牲空间复杂度换取时间复杂度,但是也不一定,具体情况具体分析
赋值语句空间复杂度为1,列表的空间复杂度是列表长度,但还是常数级,属于O(1)范畴
下面是一篇大佬的总结,我就抛砖引玉了
时间复杂度与空间复杂度详解 —— 如何计算 + 如何表示_如何理解空间复杂度的表示-CSDN博客
二、枚举法
1.常用语句
暴力破解,使用for循环或者while循环,逐个列举,逐个排查,比较费事,但准确率高
2.解题思路
①确定枚举对象,枚举范围和判断条件
②逐一列举可能的解,验证是否为本问题的答案
3.解题步骤
①猜可能解的范围,做到不重不漏
②找到判断真解的条件(把题目的条件进行翻译)
③缩小可能解的范围,找到另一个隐藏条件,缩圈再运算,提高效率
三、解题实例
1.开胃小菜:求一个符合条件的五位数
算法描述题x算=题题题题题
不同的字代表不同的数字,请找到“算法描述题”对应的五位数
按步骤解题:
①确定枚举对象,猜范围:求的是某个五位数,那么最明显的范围就是(10000,99999)
②找判断条件:各个位的数均不同(这个不好描述,pass),个位数与11111的某个倍数的每一位数值相同(这个关于到位置,可以用字符串,选取)
③缩圈:还注意到,这个不重复的五位数可以整除11111(可以用整除缩小范围)
完整代码如下:
for i in range(10000,99999):#遍历五位数
for j in range(0,10):#“算”不能是0.1.2
if i*j%11111==0: #11111的倍数
if len(str(i))==len(set(str(i))): #五个位都不同
if str(j)==str(i)[0]: #“题”相同
print(i)
2.关灯问题
1表示亮,0表示灭,下面的图引用知乎
Flip Game(又名翻转游戏、点灯游戏、灭灯游戏)的游戏技巧是什么? - 家飞猫的回答 - 知乎
https://www.zhihu.com/question/22716573/answer/62708510
import numpy as np
line=[[0]*6]* 5
for i in range(5):
line[i] = input("请输入第"+ str(i)+"行:").split(',')#将 line 中的元素转换为整型
line[i] = list(map(int, line[i]))
puzzle = np.array(line)
zero =np.zeros(6)
#向puzzle 中的最上面加入一行 0
puzzle =np.insert(puzzle,0,values=zero,axis=0)
#向puzzle 中的最后一列加入一列 0
puzzle =np.insert(puzzle,6,values=zero,axis=1)
#向puzzle 中的第0列加入一行 0
puzzle =np.insert(puzzle,0,values=zero,axis=1)
b=[[0 for col in range(8)]for row in range(6)]
#6*8 不要写反
press = np.array(b)
#或press=np.zeros((6,8))
def guess():
for r in range(1,5):
for c in range(1,7):
#根据 press 的第一行和 puzzle 的第一行,确定 press 其他行的值
press[r + 1][c]=(puzzle[r][c]+press[r][c]+ press[r - 1][c]+press[r][c - 1]+press[r][c + 1])%2#判断所计算的 press 能否熄灭最后一行的所有灯
for c in range(1,7):
if(press[5][c - 1]+ press[5][c] + press[5][c + 1]+ press[4][c])%2 !=puzzle[5][c]:
return 0
return 1
#枚举第一行按下开关的所有可能性,有2~6个
def enumeration():
while guess()==0:
press[1][1]+=1
c =1
while(press[1][c]>1):
press[1][c]=0
c += 1
press[1][c]+= 1
continue
enumeration()
print("灯的初始状态:\n",puzzle[1:6,1:7])
print("按下结果为:\n",press[1:6,1:7])
上面的代码实现的是按一次的结果
3.青蛙踩水稻问题
稻田里有青蛙,青蛙会踩着水稻跳出去,同一个水稻田有多只青蛙的跳跃路径,农夫只能看见x-y方向的青蛙路径,询问:跳的最多的青蛙跳了几步?
这是完整的实现代码
max=2
RC = input("请输入水稻的行数和列数:").split(' ')
RC = list(map(int,RC))
n = int(input("请输入被踩踏的水稻数目:"))# 输入被踩踏的水稻坐标
plants=[[0,0]]*n
plant =[0,0]
for i in range(n):
plants[i]= input("请输入第"+ str(i + 1)+"棵被踩踏的水稻的坐标:").split(' ')
plants[i]= list(map(int, plants[i]))
def searchpath(secplant,dx,dy):
plant[0]= secplant[0] + dx
plant[1]= secplant[1] + dy
steps =2
RC =[6,7]
while 1 <= plant[0] <= RC[0] and 1 <= plant[1] <= RC[1]:
if plant not in plants:
steps = 0
break
plant[0] += dx
plant[1] += dy
steps += 1
return steps
# 将被踩踏的水稻按坐标大小进行排序,先比较,若x相等,则比较y
plants = sorted(plants)
for i in range(n -2):
for j in range(i + 1, n - 1):
# 选取 plants[i]为第一个点,plants[j]为第二个点,求取间距 dX 和 dy
dX = plants[j][0]- plants[i][0]
dY = plants[j][1] - plants[i][1]
# 判断选取的第一个点的前一个点的坐标是否在稻田里
# 若在,说明选取的步长太小,换第二个点再试
pX = plants[i][0] - dX
pY = plants[i][1] - dY
if 1 <= pX <= RC[0] and 1 <= pY <= RC[1]:
continue
# 跳跃 max步后,判断其在x方向是否过早越界
# 其中max是程序实时比较计算出的最大步数
# 由于 plants 是排序过的,当这个点越界时,换第二个点,dx增大
# 那么无论怎么换第二个点,都一定越界,因此,换第一个点再试
if plants[i][0]+(max-1)* dX> RC[0]:
break
# 跳跃 max步后,判断其在¥方向是否过早越界
# 由于 plants 是排序过的,换第二个点时,dx增大,但是 dy 减小
# 因此,如果越界,则需换第二个点再试
pY = plants[i][1] + (max - 1) * dY
if pY > RC[1] or pY < 1:
continue
# 走到这步说明,该条路径不仅符合条件,而且跳跃步数比之前的max要大#因此,计算该条路径的步数
steps = searchpath(plants[j], dX, dY)
if steps > max:
max = steps
if steps == 2:
max = 0
print(max)
以下为测试结果
以上为笔者的学习笔记,如有不妥,欢迎各位批评指正0v0