哈喽,大家好!今天想跟大家分享三个最近刚刷的递归算法类型的题目,虽然题目类型标榜为简单,但是我感觉做出来尊的不简单啊。呜呜呜~~终究还是我太菜了。那就通过整理题目慢慢进步吧!一起刷题一起进步!!
目录
一、递归实现指数型枚举
问题描述
从1~n这n个数中随机选取任意多个,输出所有可能的选择方案。
输入格式
输入一个整数n。
输出格式
每行输出一种方案。
同一行内的数必须升序排列,相邻两个数用恰好一个空格隔开。
对于没有任何数的方案,输出空行。
数据范围
输入样例
3
输出样例
3
2
2 3
1
1 3
1 2
1 2 3
问题的分析
对于本题,可以把输入的整数n想象成有n个坑,需要数字对应填入,每个坑可以填入数字也可以选择不填数字。以n=3为例,该问题的递归搜索树如下图
代码实现
n = int(input())#n表示最多有n个位置需要填数
# state列表存放当前每个位置是否填数,0表示正在考虑,1表示该位置填数,2表示该位置不填数
state = [0 for i in range(n)]
# 定义递归函数,u表示正在枚举索引为几的位置是否填数
def dfs(u):
if u == n:#递归边界
for i in range(n):#每个位置按字典序排序输出
if state[i] == 1: #如果该位置放了数,就将其输出
print("%d"%(i+1),end=" ")
print()#每种可能换行表示
return #return必须有,表示递归结束。如果没有会产生列表索引越界报错
# 因为每个结点有两种情况,考虑每个结点的两个分支
# 考虑第一种情况为该位置不放数
state[u] = 2
dfs(u+1)#递归搜索子树
# 恢复现场
state[u] = 0#该语句可以不加
# 考虑第二种情况为该位置放数
state[u] = 1
dfs(u+1)#递归搜索子树
# 调用递归函数,从第一个位置开始递归枚举
dfs(0)
二、递归实现排列型枚举
问题描述
把1~n这n个整数排成一行后随机打乱顺序,输出所有可能的次序。
输入格式
一个整数n。
输出格式
按照从小到大的顺序输出所有方案,每行一个。
首先,同一行相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标一一比较,字典序较小的排在前面。
数据范围
输入样例
3
输出样例
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
问题分析
以输入数据3为例,对1~3三个数字进行全排列,并且按照字典序较小的排在前面。将为题转化为有三个坑,每个坑上必须填入数字,遍历每一个坑,每个坑上都填入之前没有出现过的数字。对此绘制递归搜索树如下图
代码实现
n = int(input())#表示对n个数进行全排列,有n个坑需要填数
# 因为每个坑不能填用过的数,所以需要有一个uesd列表,记录该数是否被用过
used = [0 for i in range(n)]#初始化时设为0,0表示该数没有被用过,1表示该数被用过
# ways列表记录每个位置填入了哪个数,初始化设为0,0表示该位置还未填数
ways = [0 for k in range(n)]
# 递归函数,u表示从索引为u的坑位上搜索
def dfs(u):
if u == n:#递归边界,当u==n时表示所有的坑位已经填满,可以将坑位上的数字输出
for i in range(n):#遍历每个坑位,并将其输出
print("%d"%ways[i],end=" ")
print()#每种遍历方式换行输出
return
# 遍历每一个分支
for i in range(n):
if used[i] == 0:#当该数没有输出时
ways[u] = i + 1 #
used[i] = 1#u索引位上填入了该数,则记录该数已经填过
dfs(u+1)#搜索当前结点的下一个分支
# 恢复现场
used[i] = 0
ways[u] = 0
# 表示一开始从索引为0的坑位上搜索
dfs(0)
运行结果
三、递归实现组合型枚举
问题描述
从1~n这n个整数中随机选出m个,输出所有可能的选择方案。
输入格式
两个整数n,m在同一行用空格隔开。
输出格式
按照从小到大的顺序输出所有的方案,每行1个。
首先,同一行的数升序排列,相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如 1 3 5 7排在1 3 6 8前面)。
数据范围
输入样例:
5 3
输出样例:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
问题分析
输入的n为一共有1~n个数,输入的m为表示有m个坑需要填数。其中需要保证m个坑中前一个坑填的数比后一个坑填的数小。绘制递归搜索树如下图:
代码实现
n,m = input().split()
n = int(n)#表示有1~n个整数
m = int(m)#表示有m个坑等待填数
# 填过的数不能再填,uesd列表记录该数是否被填过,0表示未被填过,1表示该数被填过
used = [0 for i in range(n)]
# ways列表记录每个坑填入的数,初始化为0,表示还没填数
ways = [0 for k in range(m)]
stop = n//(m-1)+1
# 递归函数,u表示从索引为u的坑,next表示从第几个数开始搜索
def dfs(u,next):
if u == m:#递归边界,表示枚举到最后一个坑的下一个坑时结束枚举,输出每个坑里的数
for i in range(m):
print("%d"%(ways[i]),end=" ")
print()
return
# 遍历每一个分支,并且每一个分支从上一个选择的数后面开始枚举
for i in range(next,n):
if used[i] == 0:
ways[u] = i+1
used[i] = 1
dfs(u+1,i+1)
# 恢复现场
used[i] = 0
ways[u] = 0
# 表示第一个坑,从第一个数开始搜索
dfs(0,0)
运行结果
总结
关于递归枚举问题,需要通过输入和输出的样例进行问题的分析,画出递归搜索树。根据搜索树进行代码的书写。递归出口就是递归边界,一定要由return语句,表示递归结束,不然会报错。掌握套路,每一次递归下一层时需要恢复现场,上述三题都是根据每个坑进行枚举的,所以有一个state或者是ways列表记录枚举到第几个坑以及每个坑上的数据。对于不能重复填入的数字,可以用used数组进行记录该位置是否被使用过。