面试题38_字符串的排列

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述:

输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母

题解

本题实际上是全排列问题,是回溯法的典型应用,在回溯法的基础加上了状态重置和剪枝。

回溯可以理解为“恢复现场”,是为了节约时间和空间的一种技巧。回溯本质上是深度优先遍历,因为用到回溯的问题通常都是在一棵树上完成的,我们使用深度优先遍历在这棵树上寻找最终的答案。

设置一个状态数组,初始化的时候状态数组中的元素都为false,表示所有元素都没有被选择。当选择一个元素时,就设置状态为true,表示当前元素被选择,在考虑下一个位置的时候就可以判断还剩余哪些元素可供选择。

注意:在判断是否满足结束条件的时候,不可以直接 res.add(路径),否则最终得到的结果会全为空。这是因为DFS完成遍历之后会撤销之前所有的选择,路径在回到根节点之后为空。

而在Java中都是值传递,在传参的过程中复制的是变量的地址。如果每次在满足结束条件的时候只是简单地 res.add(路径),这实际上所有的路径指向的都是同一块内存地址,因此在将路径添加到容器中时需要做一次深拷贝,即 res.add(new 路径)。

给出一个回溯法的框架:

res = []

def Permutation(String):
    BackTracking()
    return res

def BackTracking(选择列表, 路径, 状态):
    if 满足结束条件:
        res.add(new 路径)
        return
    for 选择 in 选择列表:
         剪枝
        做选择
        BackTracking(选择列表, 路径, 状态)
        撤销选择

参考代码:

public ArrayList res = new ArrayList<>();

public ArrayList Permutation(String str) {
    if (str.length() == 0 || str.length() >= 9)
        return res;
    BackTracking(str, new StringBuilder(), new boolean[str.length()]);
    return res;
}

// 设置flag标志进行剪枝
public void BackTracking(String str, StringBuilder sb, boolean[] flags) {
    if (sb.length() == str.length() && !res.contains(sb.toString())) {
        res.add(sb.toString());
        return;
    }
    for (int i = 0; i < str.length(); i++) {
        // 剪枝
        if (flags[i])
            continue;
        sb.append(str.charAt(i));
        flags[i] = true;
        BackTracking(str, sb, flags);
        sb.deleteCharAt(sb.length()-1);
        flags[i] = false;
    }
}

你可能感兴趣的:(面试题38_字符串的排列)