题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串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<String> res = new ArrayList<>();
public ArrayList<String> 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;
}
}
网友评论