字符串/数字列表的全排列和组合算法
1. 全排列问题
参考博客1:一次搞懂leetcode的全排列问题
参考博客2【超级棒的博客】:字符串的全排列组合算法
1.1 下一个全排列数(leetcode31)
参考博客3:https://blog.csdn.net/zr1076311296/article/details/51296008
举一个小例子:
例如 5 4 7 5 3 2 序列中是蓝色的有序的,即依次降序,可以说明,这个时候5 4 7 5 3 2 所组成的数字是最大的(绿色部分),所以下一步是找到比这个数字更大的数字,那么只能往前考虑了,即把4加入到 54 7 5 3 2 中,那么就是 54 7 5 3 2 ,意思就是找到比54 7 5 3 2 大的数字中的最小的一个。用肉眼去看,是 55 2 3 4 7 所以我们是找到了比5 4 7 5 3 2 大的数字中的最小的一个,方法是在黄色序列(7 5 3 2)中找到比红色(4)大的数字中最小的一个,然后互换两者的位置 5 57 4 3 2 ,还没完,此时还不是索要找的数字。将蓝色部分排序后 : 5 5 2 3 4 7 ,这就是慢要求的下一个数字。
算法思路:
由此,我们可以知道,本题的关键即是求出数组末尾的最长的非递增子序列。 不妨假设在数组nums中,nums[k+1]…nums[n]均满足前一个元素大于等于后一个元素,即这一子序列非递增。
那么,我们要做的,就是把nums[k]与其后序列中稍大于nums[k]的数交换,接着再逆序nums[k+1]…nums[n]即可。
python实现【待更新】
【待更新】
1.2 无重复数字的全排列数(leetcode46)【最经典】
题目描述:给定一个无重复数字的序列,返回这些数所能排列出所有序列。
方法一:【剑指offer交换的方法】
1:首先求出所有可能出现在第一个位置的字符,即把第一个字符和后面所有字符进行交换(自己与自己交换也是一种情况)
2:之后仍然把后面的字符分为两部分,即后面字符的第一个字符,以及之后的所有字符,进入递归,递归终止条件为:字符长度为1,无法进行交换了
def Permutation1(nums):
#无重复数字的全排列问题-方法1【剑指offer中交换的方法】
#有重复数字的话就先简单的用set解决把,头痛不想看
if len(nums)<=1:
return [nums]
res = []
for i in range(len(nums)):
#交换第一个字符与后面的所有字符
nums[0],nums[i] = nums[i],nums[0]
for j in Permutation1(nums[1:]):
res.append([nums[0]]+j)
nums[0],nums[i] = nums[i],nums[0]
return res
方法二:
采用一个一个向中间集添加元素的方法,递归终止条件是传入的待递归的列表长度<=1
【此种方法的优点是无需交换,因此此种方法可以稍加改变的用于字符串的排列问题,因为在python中字符串是不可变类型,且如果输入字符是按序输入的话,最终输出的排列也是按照字典序输出的】
def permutations2(nums):
#无重复数字的全排列问题-方法2
#(此种方法不进行交换,即不改变字符串,可以不加改变的用于字符串的排列问题)
# 而且如果最开始的列表是有序的,输出结果也能保证有序
if len(nums)<=1:
return [nums]
res = []
for i in range(len(nums)):
#每一次先选出列表中的一个数作为首位数,然后剩下的列表递归下去
tmpres = permutations2(nums[0:i]+nums[i+1:])
for j in tmpres:
res.append([nums[i]]+j)
return res
其实方法一和方法二的实质是一样的,都已先找出首位,再递归找首位,第一种方法中由于使用了交换,每次都是把nums[1:]递归下去,【循环中还要记得把交换恢复回来】,第二种方法中由于没有使用交换,所以每次需要把nums[:i]+nums[i+1:]递归下去
1.3 有重复数字的全排列【并且正序输出】
目前笔者只简单的用set集合和python自带的sort函数实现了功能,其他方法详见参考博客2
这里需要注意的是set是无序集合,python不保证其中元素的次序。打印结果取决于其内部存储结构和输出方式。
不改变顺序举例
#原始方法,但是会打乱顺序
mylist = [1,2,2,2,2,3,3,3,4,4,4,4]
myset = set(mylist) #myset是另外一个列表,里面的内容是mylist里面的无重复 项
# 收件人去重,并保持原来的收件人顺序
mailto = ['cc', 'bbbb', 'afa', 'sss', 'bbbb', 'cc', 'shafa']
addr_to = list(set(mailto))
addr_to.sort(key = mailto.index)
1.4 利用python的库进行解决【itertools】
参考:https://blog.csdn.net/mishi_zcf/article/details/52455688
2.组合问题【参见参考博客2,python代码后续补充】
方法一:求和C(n,m)=C(n,0)+…+C(n,n)
# 数组的组合问题
# 用递归的方式,遍历字符串,每个字符只能取或不取。若取该字符,就把它放到字符串中,遍历完毕后,输出字符串。 先能够print出来
def Combine1Recursion(array,begin,length,tmpres):
# array表示输入数组 begin表示当前遍历到哪个数了 length表示C(n,m)中的m,即组合的长度[0-n]
# tmpres表示中间结果
if length == 0:
#说明该输出了
print(tmpres)
return
if begin == len(array):
#说明没有数字可以放入了
return
#放入当前数字
tmpres.append(array[begin])
Combine1Recursion(array,begin+1,length-1,tmpres)
#不放入当前数字
tmpres.pop()
Combine1Recursion(array,begin+1,length,tmpres)
def Combine1(array):
N = len(array)
tmpres = []
for i in range(N+1):
Combine1Recursion(array,0,i,tmpres)
Combine1(['a','b','c'])
方法二:模拟二进制数的进位
def get_subset(array,subi):
#i表示一个二进制数
res = []
for i in range(len(array)):
if subi & (1<<i):
#这里其实有个小trike,因为位运算判断要不要加入这个数字肯定是从最后面开始向前判断的
#但位了最后输出有序,我们从数组的第一个开始加入,其实最后只是顺序上的差别
res.append(array[i])
return res
# 采用位运算的方法,一共应该有2**n种组合【算上[]】
def Combine2(array):
n = len(array)
for i in range(1<<n):
print(get_subset(array,i))
Combine2(['a','b','c'])
总结
排列问题就是先选出一个第一个数,然后不断的递归下去,不断的选出下一个第一个数
组合问题就是遍历每一个数,判断是否要加入这个数,加入的话如何递归,不加入的话又如何递归的问题
两个问题都可以用递归解决,只是递归终止条件需要不同的设计。