《剑指offer》字符串的排列(全排列,重复字符串和非重复字符串的处理)

题目:

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值