字符串的全排列
offer34的题目要求是先输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母,然后输出它的全排列。
for example:输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba
做类似的排列组合问题时,我们的思路还是比较固定的:把一个字符串看成两部分组成:第一部分为第一个字符,第二部分为后面的所有字符。以此为基础来构建递归:
首先求所有可能出现在第一个位置的字符,即把第一个字符和后面的所有字符交换;然后固定第一个字符,求后面所有字符的排序。此时仍把后面的字符看成两部分,第一个字符和后面的字符,然后重复上述步骤。
对于重复值的判断,用Hashset就可以了,比如说当前首位字母为b,下一位字母也为b,则交换了还是一样的,所以可以不交换。
抄一张巨佬画的图,我自己画的实在是丑不忍睹……
根据上面的递归思路,我们可以写出代码:
# offer34-solution
class Solution:
def Permutation(self, ss):
# write code here
if not ss:
return []
if len(ss) == 1:
return list(ss)
pStr = []
charlist = list(ss)
charlist.sort()
for i in range(len(charlist)):
if i > 0 and charlist[i] == charlist[i - 1]: # 此时两个字符相同,结束该次循环
continue
temp = self.Permutation(''.join(charlist[:i]) + ''.join(charlist[i + 1:])) # 除去i字符后递归
for j in temp:
pStr.append(charlist[i] + j) # 序列++
return pStr
还有其它大佬给出了别的方法——字典序排列方法:这个方法的核心思想是全排序,通过找到数列的顺序,即相邻两个数列要有尽可能长的共同前缀,将变化限制在尽可能短的后缀上,最后生成一系列按字典序排的数列。以及堆栈的方法,但是我一个搞数据的,才疏学浅,递归已经是极限了哎……
字符串的组合
有排列自然有组合,当交换字符串中的字符时,虽然能得到两个不同的排列,但却是同一个组合。比如 ab 和 ba 是不同的排列,但只算一个组合。
有了上面的经验,我们同样可以通过递归来解决这个问题。
# offer34-continuation 1
def get_combination(str,res=[],str_comb=""):
if len(str_comb) >0 and str_comb not in res:
res.append(str_comb)
if len(str) == 0:
return res
comb1 = str_comb+ str[0]
res = get_combination(str[1:],res,comb1)
res = get_combination(str[1:],res, str_comb)
return res
8皇后问题
掌握了排列组合,我们来研究一下8皇后问题——这个问题实在太经典了。
8皇后问题:在8*8的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处于同一行,同一列或者同意对角线上,求出所有符合条件的摆法。
思路:由于8个皇后不能处在同一行,那么肯定每个皇后占据一行,这样可以定义一个数组A[8],数组中第i个数字,即A[i]表示位于第i行的皇后的列号。先把数组A[8]分别用0-7初始化,接下来对该数组做全排列,由于我们用0-7这7个不同的数字初始化数组,因此任意两个皇后肯定也不同列,那么我们只需要判断每个排列对应的8个皇后中是否有任意两个在同一对角线上即可,即对于数组的两个下标i和j,如果有
i
−
j
=
A
[
i
]
−
A
[
j
]
o
r
i
−
j
=
A
[
j
]
−
A
[
i
]
i-j=A[i]-A[j] or i-j=A[j]-A[i]
i−j=A[i]−A[j]ori−j=A[j]−A[i]则认为有两个元素位于了同一个对角线上,则该排列不符合条件。
代码如下:
# offer34-continuation 2
def confict(state, pos):
nextY = len(state)
if pos in state: return True
'''判断斜线'''
for i in range(nextY):
if nextY-pos == i-state[i]: return True
if nextY+pos == i+state[i]: return True
return False
def queens(num, state=()):
if num-1 == len(state):
for i in range(num):
if not confict(state, i):
yield (i,) # 类似一个return
else:
for pos in range(num):
if not confict(state, pos):
for result in queens(num, state+(pos,)):
yield (pos,) + result
for i in list(queens(8)):
print (i)
N皇后问题
除了将8改成N以外没有任何区别,在不考虑优化的情况下,写递归的方式是一样的,常见的是N=4和N=8,下面抄别人的代码了,文末给出参考链接。
# offer34-continuation 3
class Solution:
def solveNQueens(self, n):
res = set() # 存放每行的皇后放置的坐标
# 递归
def find_pos(row_index, queen, n):
for j in range(n):
if pos_available(queen, (row_index, j)):
queen[row_index] = j
if row_index == n-1: # 已经是最后一行并且找到了可以放的位置
res.add(tuple(queen))
return
else: #没到最后一行
find_pos(row_index+1, queen, n)
# 检验当前位置是否可用
def pos_available(queen, pos):
# 放第i行 根据0~i-1行进行判断
for i in range(pos[0]):
if queen[i] == pos[1]: # 同列
return False
elif abs(i-pos[0]) == abs(queen[i]-pos[1]): # 主副对角线
return False
return True
# 把坐标形式打印成 Q...形式
def plot_queen(queen, n):
out = []
for i in range(n):
s = '.' * queen[i] + 'Q' * 1 + '.' * (n-queen[i]-1)
out.append(s)
return out
# 初始皇后位置-1 其实没有影响
queen = [-1 for _ in range(n)] # 初始位置
# 递归 从第0行开始
find_pos(0, queen, n)
out = []
for t in res:
out.append(plot_queen(t, n))
return out
类似问题的递归
通过以上四个问题,我们其实可以构建一个类似问题的递归模板:
# offer34-continuation 4
'''
类似问题递归通用模板:
for(之前的选择序列):
若当前是最后一次选择:
遍历选择的所有取值:
此次取值不与之前的选择冲突:
元组(或列表)形式“返回”该值
当前不是最后一次选择:
遍历所有取值:
不与之前的选择序列冲突:
“返回”当前选择取该值的基础上,接下来的选择结果。
'''
以后还有什么奇怪的问题,可以直接套用这个模板。类似的问题比如有:输入一个含有8个数字的数组,判断有么有可能把这8个数字分别放到正方体的8个顶点上,使得正方体上三组相对的面上的4个顶点的和相等。还有其它更多,不再一一枚举。