字符串全排列(无重复)
-
问题描述
输入一个无重复字符的字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。 -
解题思路
对于字符串中的每一个字符,将当前字符之后(包含当前字符)的所有字符都与之交换,直到遍历完整个字符串,此时遍历得到的所有字符串即是所有排列的可能性。
这么说可能不是很容易理解,结合图来看:
OK,画的有点丑,凑合看。
假设现在有字符串 abc
,我们需要列出它的全排列可能情况。
- 对于字符串的第一个,也就是第0位字符
a
,我们需要将字符串之后的,包括它本身在内的所有字符都与之交换,那么我们可以得到三组不同的字符串组合{abc, bac, cba}
。 - 对1中得到的三个字符串组合
{abc, bac, cba}
,重复1的过程,不过需要注意的是,这时是从第1位字符开始。举个栗子,对于abc
来说 ,此时它的第1位字符是b
,那么需要将b
之后的字符包括它本身与之交换,即需要交换两次b <=> b
,b <=> c
,那么得到两组不同的字符串组合{abc, acb}
。 - 对于2中得到的字符串组合,重复以上的过程,此时其实已经遍历到最后一个字符了,不做交换也可以,直接将字符串本身输出即可。这时我们就得到了所有的排列组合可能性。
- Python 代码实现
class Solution:
def __init__(self):
self.slist = []
def Permutation(self, ss):
if len(ss)==0:
return self.slist
# 将字符串转换为列表
ssl = list(ss)
begin = 0
self.sPermutation(ssl,begin,len(ssl))
return self.slist
def sPermutation(self,ss,begin,end):
if begin == end:
# 将列表转换为字符串,再存放
self.slist.append(''.join(ss))
for i in range(begin, end):
# 交换字符
ss[i], ss[begin] = ss[begin], ss[i]
# 递归实现
self.sPermutation(ss,begin+1,end)
# 防止循环下次执行时,字符串被修改
ss[i], ss[begin] = ss[begin], ss[i]
简单解释一下代码实现思路,虽然是依据解题思路来的,但是实现上却有不同,以 abc
为例。
- 初始化
begin
为 0,将"abc"
转换为["a", "b", "c"]
,为了方便交换字符的位子(Python 中 String 不可被修改)。 - 将
["a", "b", "c"]
输入到sPermutation()
函数中,判断begin
是否是最后一个字符的索引,如果是表示遍历完成,可以将当前的字符串保存下来了。 - 将字符串从
begin
位开始向后遍历,并将遍历的字符与begin
位的字符交换位置。 - 注意看,此时并不会将循环执行完,而是下一步直接将begin加一,进入递归迭代。而在第3步时候,其实循环才走了第一步,此时
i = begin
,也就是a
与a
本身交换了;那么进入递归之后,循环也是走了第一步,此时i=begin=1
,也就是b
与b
交换;同理又进入递归,此时无法执行到c
与c
交换,因为begin = end
,已经遍历到最后一个字符了,此时会把abc
存入到列表slist
的第0位。 那么递归到此结束,退回到上一层,也就是b
与b
交换的那一层,这一层的self.sPermutation(ss,begin+1,end)
执行完成,存储了abc
到列表slist
的第0位。 - 然后
b
与b
交换的这一层继续往下执行,此时 ss 里面是abc
,但是需要注意的是,这里的b
是交换过的,因为交换的是b本身,所以字符串没有改变。因此我们需要将交换过的字符串再交换回来,才能进行循环的下一步。 - 循环进入下一步,此时
begin
依旧等于1,但是i
等于2了,即:ss[begin] = b, ss[i] = c
。交换二者的位置,此时ss = ["a", "c", "b"]
。进入下一次迭代,将["a", "c", "b"]
存入至列表slist
的第1位。然后退回到本层,因为此时 ss 变为了 [“a”, “c”, “b”],因此需要将bc交换回来。至此begin
等于1的这层完全执行完毕,退回至begin
等于0的这层。 begin
等于0的这层,先将a
与a
再交换回来,然后进入下一步循环i = 1
,交换ss[0] = a
与ss[1] = b
的值,得到ss = ["b", "a", "c"]
,然后进入递归,重复456步。- 重复7456步。
- 得到最终结果
字符串全排列(有重复)
-
分析
对于有重复的情况,假设是abb,可以发现第二位的b与第三位的b交换和第二位的b与本身交换的结果是一样的;并且1,3交换可以得到bba,1,2交换得到bab,再2,3交换也能得到bba等等,会存在许多重复的情况。那么我们需要在交换之前添加一个判定条件,如果需要交换的字符在之前
存在有相同的字符,就不进行交换,这里需要注意,这里的之前
指的是从 begin 到 i 之间的所有字符,而不是 i 之前的所有字符,因为 begin 之前的字符并不参与这次交换,所以存在重复也不影响。 -
代码
class Solution:
def __init__(self):
self.slist = []
def Permutation(self, ss):
if len(ss)==0:
return self.slist
# 将字符串转换为列表
ssl = list(ss)
begin = 0
self.sPermutation(ssl,begin,len(ssl))
return self.slist
def sPermutation(self,ss,begin,end):
if begin == end:
# 将列表转换为字符串,再存放
self.slist.append(''.join(ss))
for i in range(begin, end):
# 添加一个判断,排除重复
if begin==i or (ss[i] not in ss[begin:i]):
# 交换字符
ss[i], ss[begin] = ss[begin], ss[i]
# 递归实现
self.sPermutation(ss,begin+1,end)
# 防止循环下次执行时,字符串被修改
ss[i], ss[begin] = ss[begin], ss[i]
字符全排列(有重复、按字典序)
所谓字典序也就是从小到大,字母就是由A-Z-a-z。
321>312,所以312在321前面;
acb>abc,所有abc在acb前面。
一般思路
在全排列之后添加一个排序算法就可以了。代码如下:
class Solution:
def __init__(self):
self.slist = []
def Permutation(self, ss):
# write code here
if len(ss)==0:
return self.slist
ssl = list(ss)
begin = 0
self.sPermutation(ssl,begin,len(ssl))
return self.resort(self.slist)
def sPermutation(self,ss,begin,end):
if begin == end:
self.slist.append(''.join(ss))
for i in range(begin, end):
if begin==i or (ss[i] not in ss[begin:i]):
ss[i], ss[begin] = ss[begin], ss[i]
self.sPermutation(ss,begin+1,end)
ss[i], ss[begin] = ss[begin], ss[i]
# 简单选择排序
def resort(self,l):
for i in range(len(l)):
for j in range(i,len(l)):
if l[j]<l[i]:
l[i],l[j] = l[j],l[i]
return l
itertools.permutations
itertools.permutations(x,s)是Python 内置的全排列函数,输入x是可迭代对象,可以是list,也可以是string;
s是组合字符串的字符个数,也可以不填,可能不好理解,举个栗:
假如输入x=“abc”,s=2,那么得到的输出是:
(a,b)
(b,a)
(a,c)
…
如果s不填,那么得到的输出是:
(a,b,c)
(a,c,b)
(b,a,c)
…
permutations 返回的是所有全排列的可能性(包含重复的排列),而 Python 也拥有内置的去重函数 set()。
set() 输入是可迭代对象,输出是一个集合。
一般所有语言都有内置的排序算法,不需要自己写排序算法,Python也有,通过sorted()调用。
综上,我们可以得到:
import itertools
class Solution:
def Permutation(self, ss):
if not ss:
return []
return list(sorted(set(map(''.join, itertools.permutations(ss)))))
虽然看着是挺简洁的,但是这个作业别的编程语言没得抄呀。
字典序思维(非递归方法)
https://www.cnblogs.com/pmars/archive/2013/12/04/3458289.html
【例】 如何得到346987521的下一个
1. 从尾部往前找第一个P(i-1) < P(i)的位置
4 6 <- 9 <- 8 <- 7 <- 5 <- 2 <- 1
最终找到6是第一个变小的数字,记录下6的位置i-1
2. 从i位置往后找到最后一个大于6的数
4 6 -> 9 -> 8 -> 7 5 2 1
最终找到7的位置,记录位置为m
3. 交换位置i-1和m的值
4 7 9 8 6 5 2 1
4. 倒序i位置后的所有数据
4 7 1 2 5 6 8 9
则347125689为346987521的下一个排列
这个思维还是很棒的。
class Solution:
def Permutation(self, ss):
# write code here
slist = []
l = len(ss)
if l==0:
return slist
# 从小到大排列输入字符串
ssb = ''.join(sorted(ss))
# 增加第一个排列可能性
slist.append(ssb)
ssb = list(ssb)
# 从大到小的排列,定边界
sse = ''.join(sorted(ss,reverse=True))
while(slist[len(slist)-1] != sse):
# 从后往前遍历
for i in range(l-1,-1,-1):
# 发现降序排列的两个相邻字符
if ssb[i-1]<ssb[i]:
for j in range(l-1,i-1,-1):
if ssb[j]>ssb[i-1]:
# 交换两个字符的值
ssb[j],ssb[i-1] = ssb[i-1],ssb[j]
if i != (l-1):
k = i
# 倒序排列i之后的字符,python2需要 /2.0
while(k<((i+l-1)/2)):
ssb[k],ssb[i+l-1-k]=ssb[i+l-1-k],ssb[k]
k+=1
slist.append(''.join(ssb))
break
break
return slist
这里 k<((i+l-1)/2)
这句,需要注意Python2与Python3的区别,Python2的除法会向下取整,所以需要改成 k<((i+l-1)/2.0)
。