题目描述
牛客地址:
https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7
题解
思路:
题意描述很简单,就是将一个字符数组的所有排列组合都列举出来即可.我们可以利用分治的思想,将大问题逐级分解成小问题,最终再汇总.
我们可以将思路整理成下面三点:
1.我们可以先固定第一个字符,求剩下n-1个字符的排列情况.求剩下n-1字符的排列情况,就变成了一个子问题,最终我们可以分解成求1个字符的排列情况.这里我们可以利用栈的思路逐级向下递归.
2.第一个字符其实不是固定的,它可能会换成别的字符,因此这里需要回溯,将第一个字符改成别的字符,然后又继续求剩下n-1个字符的排列情况.
3.如果有多个相同的字符,排列可能出现重复排列的情况,因此我们可以先将数组进行排序,将相同的字符放在一起,这样如果下一个遇到的字符和上一个相同,那么我们就可以直接跳过去.因为它们两个交换不会产生新的排列.
将思路用图解表示出来如下:
代码
import java.util.ArrayList;
/**
本题主要目标就是实现一个字符串数组的全排列,如何实现一个字符串数组的全部排列的情况?答案是递归+回溯.
我们可以采用分治的思想,利用递归将问题的规模逐渐缩小到一个字符的情况,这时候其实就是从头到尾的一次遍历,
也是一次全排列.然后就开始退栈,也就是从尾部最后一个字符开始重新进行排列组合,直到把所有的排列情况都列举出来即可.
*/
import java.util.Arrays;
public class Solution {
public ArrayList<String> res = new ArrayList<String>();
public boolean[] isVisited;
public ArrayList<String> Permutation(String str) {
if(str ==null || str.length() == 0) return res;
//转化成char数组,因为需要排序
char[] strArr = str.toCharArray();
//排序的原因是将相同的字符放在一起,方面后面进行去重
Arrays.sort(strArr);
//设置一个是否访问过的标志位数组,方便后面回溯的时候判断
isVisited = new boolean[str.length()];
StringBuffer temp = new StringBuffer();
//开始进行递归
recursion(strArr,temp);
return res;
}
public void recursion(char[] strArr,StringBuffer temp){
if(strArr.length == temp.length()){
res.add(new String(temp));
return;
}
for(int i=0;i<strArr.length;i++){
//如果被访问过了,则跳过
if(isVisited[i]) continue;
//这一点很容易被忽略掉,如果前一个和自己相同,且前一个没被访问过,则直接跳过
//因为即使加进temp,也是重复的.
//比如baa,若此时已经递归到最后一个a,这时候会回溯a(即temp删除a,a设置为未访问)
//,接着再回溯a(temp又删除a,中间a设置为未访问),这时候for循环会在中间a的位置,
//当for循环遍历下一个位置a,将最后一个a先加入temp时,且再进入递归的时候,
//会再次想要将中间的a加入temp,这时候这个判断就起作用了
if(i>0 && strArr[i] == strArr[i-1] && isVisited[i-1] == false) continue;
//符合条件的加入到temp中去
temp.append(strArr[i]);
//标记已经访问过了
isVisited[i] = true;
//开始进行递归,分治
recursion(strArr,temp);
//递归回来的时候,要进行回溯
isVisited[i] = false;
temp.deleteCharAt(temp.length()-1);
}
}
}
复杂度分析:
时间复杂度:O(n∗n!),全排列的全部情况为n!,每次递归过程都是遍历字符串查找元素,这里是O(n)
空间复杂度:O(n),递归栈的最大深度为字符串长度n,临时字符串temp的空间也为O(n),res属于返回必要空间