字符串排列+组合

字符串排列+组合

问题描述:给一个字符串,打印出这个字符串中字符的所有排列。【来自剑指offer】

思路:开始的思路就是递归,因为字符串的排列可以将其看做第一个字符加上后面字符的全排列,这样就有了递归的递归公式。

由于第一个字符可以有很多中选择,就相当于是递归的入口有多种选择,需要用一个for循环来表示。

对于这个问题,需要注意的是递归入口并不只是一个,每一个字符都可以作为递归入口,因此在递归的核心代码中是包含for循环的。

一开始的错误思路是:

  1. 通过for循环每次选取入口字符;
  2. 在原字符串中将这个字符去掉形成新的递归字符串;
  3. 将新字符串输入递归函数;
  4. 最终的答案是由选取字符+递归返回值形成;
  5. 最后统一打印。

这里的错误主要实在第4步,因为这一步需要get到递归返回值,因此需要在for循环中写return,但是一到return for循环后面的就不能执行了。

因此做出的改进是将最后的统一打印步骤放在边界处,也就是递归到最后一个字符时打印,同时,通过一个栈来记录出经过的路径,字符进入压栈,返回出栈,最后将这个栈打印。这个思路就像是树的path打印,只不过这颗树不是二叉而是多叉的。

这个栈的作用以及顺序其实是和系统栈一样的,不过系统栈程序内不可见,因此虽然答案在最后的栈顶已经确定,但为了获取前面的字符还是需要一步步返回栈底,而这个题又受到return限制,因此可以人为构造一个栈,模拟压栈出栈过程。这个操作适合递归操作的正向打印。

def permutation(strs, res):
    """
    :param strs: 字符串
    :param res:  全局可见的栈
    :return: res
    """
    if len(strs) == 1:
        res.append(strs[0])
        print(''.join(res))
        res.pop()
        
    for i in range(len(strs)):
        res.append(strs[i])
        permutation(strs[0:i] + strs[i+1:], res)
        res.pop()
        
strs = ['a', 'b', 'c', 'd']
permutation(strs, [])

剑指offer上提供的思路有所不同,但同样需要在递归到栈顶的时候可以获得全局的排列状态,除了构造栈之外,可以采用直接改变输入字符串的方式,对于第一个字符,就可以通过依次与后面字符交换的方式完成对第一个字符的排列,在该层重新回到栈顶时再将字符换回来。这样的方式只需要一个额外的指针来指向该第几个字符做交换,就不需要额外的O(N)空间。

def permutation(strs, index):
    if index == len(strs) - 1:
        print(strs)
        return

    for i in range(index, len(strs)):
        strs[index], strs[i] = strs[i], strs[index]
        permutation(strs, index + 1)
        strs[index], strs[i] = strs[i], strs[index]
        
strs = ['a', 'b', 'c', 'd']
permutation(strs, 0)

总体来说,两种方法时间复杂度相同,剑指offer上提供的方法没有使用额外空间,仅在原有输入上进行操作,方法更优一些。
后面又补充了一下字符串的组合。字符串组合跟排列的思路有一些不同。首先体现在递归的入口上,由于组合的字符长度可以由1-N(N个字符),因此需要用for循环表示递归入口。

def combination(nums):
    for i in range(1, len(nums) + 1):
        combination_core(nums, i, i, [])

对于递归的核心函数,思想是将求n个字符组成长度为m的组合问题表示为两个子问题:1.求n-1个字符中长度为m-1的组合;2.求n-1个字符中长度为m的组合。

def combination_core(nums, index, length, res):

    if len(res) == index:
        print(res)
        return
    if nums == []:
        return
    res.append(nums[0])
    combination_core(nums[1:], index, length - 1, res)  # 包含第一个字符,从剩余的取出length-1
    res.pop()
    combination_core(nums[1:], index, length, res)      # 不包含第一个,从剩余的取出length个字符
    return

参数中的number来记录还剩余字符的长度,length用来记录遍历终点快速返回,result参数来记录当前结果,以便打印。

猜你喜欢

转载自blog.csdn.net/LHaoRax/article/details/89334454