《剑指offer》34、字符串的全排列及其延伸(排列组合的递归与N皇后问题)

字符串的全排列

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] ij=A[i]A[j]orij=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个顶点的和相等。还有其它更多,不再一一枚举。

参考文献

排列组合算法(递归)1
N皇后问题(Python实现)
剑指Offer-字符串的排列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值