题目:
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
思路:
不需要去重的思路:
1、递归类
取第一个数,依次和剩下的数进行位置交换。如果要交换的数,已经在该数前面几个数中出现过了,则不交换。否则交换完后,就把除第一个数的剩余数进行全排列。全排列完了记得把字符串交换归位后,再进行第一位的依次交换。
举例: bab,
取b依次与bab第一个b, ab中的a、b顺次交换。
首先是b与b的交换,第一个b前面没有字符了,所以直接对ab进行全排得到ab和ba,最终得到bab和bba。
将字符串还原得到bab,继续b与ab中a的交换。发现a前面只有b,没有重复,所以直接交换得到abb,将bb全排得到bb,最后得到字符串abb。
将字符串还原得到bab,继续bab中,b与b的交换,因为b在前面ba中出现过了,所以重复不交换,没有得到字符串。
具体讲解:
把s[0]固定在位置0上,[1,n-1]位置的数字全排(递归)
把s[1]固定在位置0上(把s[1]和s[0]交换),[1,n-1]位置的数字全排(递归)
……
如果第i个数字在前面出现过,则跳过
……
把s[n-1]固定在位置0上(把s[n-1]和s[0]交换),[1,n-1]位置的数字全排(递归)
from copy import deepcopy
def isduplicate(li, n, t):
"""
从li的位置n到位置t-1,有没有和li[t]相等的数字
"""
while n < t:
if li[n] == li[t]:
return True
n += 1
return False
def swap(li, i, j):
if i == j:
return
temp = li[j]
li[j] = li[i]
li[i] = temp
def permutation(li, size, n, result):
"""
[n,size]位置的数字全排
:param li:字符串数组
:param size: 字符串长度
:param n: 要交换的位置
:param result: 保留结果
:return:
"""
if n == size - 1:
result.append(deepcopy(li))
return
for i in list(range(n, size)): # 分别把(size-n)个数字固定到位置n
if isduplicate(li, n, i): # 如果位置n出现过数字li[i],跳过
continue
swap(li, i, n) # 把s[n]和s[i]交换,把s[i]固定到位置n
permutation(li, size, n + 1, result) # [n+1,size-1]位置的数字全排
swap(li, i, n) # 把s[n]和s[i]交换回来
if __name__ == '__main__':
li = [1, 2, 2, 3]
size = len(li)
n = 0
result = []
permutation(li, size, n, result)
for i in result:
print(i)
2、非递归:
起点:字典序最小的排列,例如1223455。起点为正序
终点:字典序最大的排列,例如5543221。终点为倒序
过程:按当前的排列找出刚好比它大的下一个排列
如:524321的下一个排列是531224
方法:
先将字符按字典从小到大排序(正序)如字符串1223455,
开始循环,然后从后往前找,先找到第一个从左到右是升序的字符位置,满足当前字符char_n<后一个字符char_n+1,如45就满足了,标记4的位置,从4往后找找到大于4的第一个最小数5,并和4交换得到54,将剩下的字符串5翻转得到5,最后得到刚好的大的排列为1223545.
对于1223545,找到45升序,45交换54,剩下的4翻转得4,得到1223554。
对于1223554,找到35升序,找到4,交换34,得到4553,将剩下的553翻转得到355,最后得到1224355.
对于1224355,找到24升序,找到3,交换2,3,得到34255,将剩下的4255翻转得到5524,最后得到1235524.
。。。
直到找不到升序的字符串或者只剩一个了。结束循环。
from copy import deepcopy
def swap(li, i, j):
if i == j:
return
temp = li[j]
li[j] = li[i]
li[i] = temp
def reverse(li, i, j):
"""
翻转
:param li: 字符串数组
:param i: 翻转开始位置
:param j: 翻转结束位置
"""
if li is None or i < 0 or j < 0 or i >= j or len(li) < j + 1:
return
while i < j:
swap(li, i, j)
i += 1
j -= 1
def get_next_permutation(li, size):
# 后找:字符串中最后一个升序的位置i,即:S[i]<S[i+1]
i = size - 2
while i >= 0 and li[i] >= li[i + 1]:
i -= 1
if i < 0:
return False
# 查找(小大):S[i+1…N-1]中比S[i]大的最小值S[j]
j = size - 1
while li[j] <= li[i]:
j -= 1
# 交换:S[i],S[j]
swap(li, i, j)
# 翻转:S[i+1…N-1]
reverse(li, i + 1, size - 1)
return True
if __name__ == '__main__':
li = [1, 2, 3, 2, 4, 5,5]
li.sort() # 初始的li必须是正序的 [1, 2, 2, 3, 4, 5,5]
size = len(li)
result = [deepcopy(li)]
while get_next_permutation(li, size):
result.append(deepcopy(li))
for i in result:
print(i)
3、处理无重复的字符串的方法,套过来解决有重复的,必须要去重。
以下思路只能单独针对无重复字符串,对有重复字符串最后是要去重的。
思路1是:把全排列的过程看做两步,
第一步是选择最左边的字母,
第二部分然后将剩下字母进行全排列,并将两部分拼起来。
也就是 长度为n的字符串的全排列=第一个字母的选择(有n种或者去重后的k种选择)+剩下n-1个字母的全排列,注意这个方法做出来有重复,做出来的结果必须要要用set()去重。
对于abb
bab bba
bab bba
去重
abb bab bba
# -*- coding:utf-8 -*-
class Solution:
def Permutation(self, ss):
if (ss == ''):
return []
if (len(ss) <= 1):
return [ss]
sl=[]
for i in range(len(ss)):
for item in self.Permutation(ss[:i]+ss[i+1:]):
sl.append(ss[i] + item)
return sorted(list(set(sl)))
if __name__=='__main__':
sl=Solution()
# 可能包含重复的串
s='abbc'
perm_nums = sl.Permutation1(s)
print('perm_nums', len(perm_nums), perm_nums)
pass
思路2是:把全排列的过程看做两步,第一步是选择最左边的字母,第二部分然后将剩下字母进行全排列,然后将最左边的字母挨个与剩下字母调换位置。也就是 长度为n的字符串的全排列=第一个字母的选择(有n种或者去重后的k种选择)+剩下n-1个字母的全排列,这个方法做出来的有重复,需要去重。
对于babb
先排列abb
把b插入abb bab bba中,显然对于 a b bb、ab b b、 abb b是有重复的。
方法略,用第一种就可以
参考:https://blog.csdn.net/weixin_42018258/article/details/80683826