暴力递归练习(二)— 打印一个字符串的全部排列

什么是字符串的全部排列
比如字符串String s = “abc”,那么它的全部排列就是“abc”、“acb”、“bac”、“bca”、“cab”、“cba”。
其实就是字符串中所有字符在每个位置的排列组合。
和练习一中的区别在于,根据字符串转换出来的char[] arr中的每个元素都不可以丢弃。所以这个练习中会用一种叫做“恢复现场”的手段来处理。

整体的思路大致是:

  1. 因为要看每个字符在字符串中不同位置的排列组合,所以第一步还是将String转换成char[]。
  2. 所以base case同样是当遍历到数组结尾时,将拼接的存储着路径结果的path结果添加到List中。
  3. 遍历char[],获取当前位置的字符,并在集合中移除它,向下递归传递,等递归结束后,再添加回集合中。

代码

 public static List<String> permutation1(String str) {
    
    
        List<String> ans = new ArrayList<>();
        if (str == null || str.length() == 0) {
    
    
            return ans;
        }

        char[] charArr = str.toCharArray();
        List<Character> charList = new ArrayList<>();
        for (char cur : charArr) {
    
    
            charList.add(cur);
        }
        String path = "";
        process(charList, path, ans);
        return ans;
    }

    public static void process(List<Character> chars, String path, List<String> ans) {
    
    
        if (chars.isEmpty()) {
    
    
            ans.add(path);
            return;
        } else {
    
    
            int len = chars.size();
            for (int i = 0; i < len; i++) {
    
    
                char cur = chars.get(i);
                chars.remove(i);
                process(chars, path + cur, ans);
                chars.add(cur);
            }
        }
    }

其中在遍历的过程中chars.remove(i)这行代码后,递归向下调用,进行拼接,如果递归执行完,不把remove的字符加回来。会有问题!!

  1. 代码程序报错,因为递归过程中遍历的集合的长度改变了。
  2. 就算代码程序不报错,不加回来,等到我遍历 i = 2时,cur = b字符,我集合中的字符越来越少了,得不到正确答案了。

第二种解题思路:交换
整体的过程是这样的,遍历的过程中,两两进行交换,直到到达数组长度后,证明没有可换的,同样是将结果放入List中,但是需要注意的是,交换完后,同样要换回来,要不然下次的遍历字符串的顺序就变了。

代码
比如说,String s = “abc”,第一次遍历时,每个位置都跟自己进行交换,但是当 i = 2,index = 1时,
bc进行交换, 而后得到字符串 acb,将结果放入到List中,如果得到结果后,不将bc位置换回来,下次就是获取字符串“acb”的全部排列了。所以这就是恢复现场

public static List<String> permutation2(String str) {
    
    
        List<String> ans = new ArrayList<>();
        if (str == null || str.length() == 0) {
    
    
            return ans;
        }
        char[] charArr = str.toCharArray();
        process2(ans, 0, charArr);
        return ans;
    }

    public static void process2(List<String> ans, int index, char[] chars) {
    
    
        if (index == chars.length) {
    
    
            ans.add(String.valueOf(chars));
            return;
        } else {
    
    
            int len = chars.length;
            for (int i = index; i < len; i++) {
    
    
                swap(chars, index, i);
                process2(ans, index + 1, chars);
                swap(chars, index, i);
            }
        }
    }

    public static void swap(char[] chars, int i, int j) {
    
    
        char tmp = chars[i];
        chars[i] = chars[j];
        chars[j] = tmp;
    }

相同结果去重
如果此时再增加一个需要要将同样排列结果的字符串进行去重呢?String s = “cac”,因为有2个c,所以获取的全排列的结果中是有重复的值。

代码
只需要改变这个方法即可,根据ASCII的范围来确定boolean[]的大小,将char转换成ASCII的数值记录再boolean[]对应的下标处,如果为true,证明碰见过相同的字符,那就不在处理,当然,用Set也是可以的,不过最后处理时还要再次遍历一次Set,而用boolean,则更加的简便,如果判断不通过,则直接跳过。

 public static void process2(List<String> ans, int index, char[] chars) {
    
    
        if (index == chars.length) {
    
    
            ans.add(String.valueOf(chars));
            return;
        } else {
    
    
            int len = chars.length;
            boolean[] visits = new boolean[256];
            for (int i = index; i < len; i++) {
    
    
                if (!visits[chars[i]]){
    
    
                    visits[chars[i]] = true;
                    swap(chars, index, i);
                    process2(ans, index + 1, chars);
                    swap(chars, index, i);
                }
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/weixin_43936962/article/details/132477635