全排列

全排列主要结合DFS求出一个数组中所有数字的排列结果,这个结果往往需要从小到大排列。
此外,全排列还经常要求出某一个排列的前一个排列或后一个排列,此时需要用到 pre_permutation() 和 next_permutation()

算法模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def next_permutation(nums):
n = len(nums)
# traversal the list from its end
# if find the first pair of numbers that is not inverse,begin to change its positon
for i in range(n-2,-1,-1):
if nums[i] < nums[i+1]:
# begin to find the first number that are bigger than it
for j in range(n-1,-1,-1):
if nums[i] < nums[j]:
nums[i], nums[j] = nums[j], nums[i]
nums[i+1:] = sorted(nums[i+1:])
# this funtion also run one time
break
# notice that the function only run one time
break
return nums

def pre_permutation(nums):
n = len(nums)
# the traceback action of the former function
for i in range(n-2,-1,-1):
if nums[i] > nums[i+1]:
for j in range(n-1,-1,-1):
if nums[i] > nums[j]:
# swap
nums[i], nums[j] = nums[j], nums[i]
nums[i+1:] = sorted(nums[i+1:], reverse = True)
break
break
return nums

例题演示

例题一:全排列问题

题目描述:

按照字典序输出自然数 11nn 所有不重复的排列,即 nn 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

输入格式:

一个整数 nn

输出格式:

1n1 \sim n 组成的所有不重复的数字序列,每行一个序列。
每个数字保留 55 个场宽。

提示:

1n91 \leq n \leq 9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def dfs(left, visited, path):
if left == 0:
# all of the number have already been put on its position
print(' ', end = '')
for i in path:
print(i, end = ' ')
print()
for i in range(1,n+1):
if visited[i]:
continue
# hasn't been visited
path.append(i)
left -= 1
visited[i] = True
dfs(left, visited, path)
# traverse back
path.pop()
left += 1
visited[i] = False


n = int(input())
visited = [False]*(n+1)
path = []
dfs(n, visited, path)

例题二:[NOIP2004 普及组] 火星人

题目描述:
人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法。这种交流方法是这样的,首先,火星人把一个非常大的数字告诉人类科学家,科学家破解这个数字的含义后,再把一个很小的数字加到这个大数上面,把结果告诉火星人,作为人类的回答。
火星人用一种非常简单的方式来表示数字――掰手指。火星人只有一只手,但这只手上有成千上万的手指,这些手指排成一列,分别编号为 1,2,3,1,2,3,\cdots。火星人的任意两根手指都能随意交换位置,他们就是通过这方法计数的。
一个火星人用一个人类的手演示了如何用手指计数。如果把五根手指――拇指、食指、中指、无名指和小指分别编号为 1,2,3,41,2,3,455,当它们按正常顺序排列时,形成了 55 位数 1234512345,当你交换无名指和小指的位置时,会形成 55 位数 1235412354,当你把五个手指的顺序完全颠倒时,会形成 5432154321,在所有能够形成的 12012055 位数中,1234512345 最小,它表示 111235412354 第二小,它表示 225432154321 最大,它表示 120120。下表展示了只有 33 根手指时能够形成的 6633 位数和它们代表的数字:

三进制数 代表的数字
123123 11
132132 22
213213 33
231231 44
312312 55
321321 66

现在你有幸成为了第一个和火星人交流的地球人。一个火星人会让你看他的手指,科学家会告诉你要加上去的很小的数。你的任务是,把火星人用手指表示的数与科学家告诉你的数相加,并根据相加的结果改变火星人手指的排列顺序。输入数据保证这个结果不会超出火星人手指能表示的范围。

输入格式:

共三行。
第一行一个正整数 NN,表示火星人手指的数目(1N100001 \le N \le 10000)。
第二行是一个正整数 MM,表示要加上去的小整数(1M1001 \le M \le 100)。
下一行是 11NNNN 个整数的一个排列,用空格隔开,表示火星人手指的排列顺序。

输出格式:

NN 个整数,表示改变后的火星人手指的排列顺序。每两个相邻的数中间用一个空格分开,不能有多余的空格。

提示:

对于 30%30\% 的数据,N15N \le 15
对于 60%60\% 的数据,N50N \le 50
对于 100%100\% 的数据,N10000N \le 10000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def add():
# add one to the number that stored in the list
global nums
n = len(nums)

if n > 1:
# traversal from the end of the list
# when find two number that are not inverse,swap the smaller one with the first one that bigger than the number
for i in range(n-2,-1,-1):
if nums[i] < nums[i+1]:
for j in range(n-1,-1,-1):
if nums[i] < nums[j]:
# swap
nums[i], nums[j] = nums[j], nums[i]
# all of the numbers that behind nums[i] need to be sorted again
nums[i+1:] = sorted(nums[i+1:])
break
break
else:
if i == 0:
# no suitable answer
nums.sort()

n = int(input())
m = int(input())
nums = [int(i) for i in input().split()]
# add the small variable to the current number
for i in range(m):
add()
for i in range(len(nums)):
print(nums[i],end = ' ')