有重复字符的排列组合[回溯 & 剪枝去重 || set去重]

排列组合 & 回溯

  • 前言
  • 一、排列组合
  • 二、回溯排列 & 两种去重
    • 1、排序+pre去重
    • 2、set去重
  • 总结
  • 参考文献

前言

排列组合必须要枚举所有情况,但当其中有重复字符时,就会出现重复的组合,即同一位置上选择位置不同但字符相同时,则后面的一套组合都是重复的。

一、排列组合

有重复字符的排列组合[回溯 & 剪枝去重 || set去重]_第1张图片

二、回溯排列 & 两种去重

1、排序+pre去重

// 有重复字符的字符串排列组合。
public class Permutation {
    /*
    如何排列?回溯
    如何去重?为什么重?
    当回溯时,取前一个后后一个放到第i位时,且当前后相同,则会产生一套重复的排列组合;
    通过记录上一个选择的pre字符和当前字符比较,如果相等则略过,从而防止重复的一套排列组合。
     */
    public String[] permutation(String S) {
        arr = S.toCharArray();
        n = arr.length;

        //bug3:想用pre去重,那就得前后排序,让相等的字符挨在一起,否则随着pre的改变,还是会让相同位置有重复。
        Arrays.sort(arr);

        dfs(new int[n]);

        return ans.toArray(new String[0]);
    }

    // bug1:第二个参数用的HashSet pool,想让hashSet去重,那排列里面用于没有相同字符,这里需要对位置去重,采用数组hash。
    private void dfs(int[] fx) {
        if (sb.length() == n) {
            ans.add(sb.toString());

            return;
        }
        char pre = '@';// bug2:pre是用于区分当前位置的重复使用,不是前后。
        for (int i = 0; i < n; i++) {
            if (fx[i] != 1 && pre != arr[i]) {
                fx[i] = 1;
                sb.append(arr[i]);

                dfs(fx);

                sb.deleteCharAt(sb.length() - 1);
                fx[i] = 0;

                pre = arr[i];
            }
        }
    }

    char[] arr;
    int n;
    List<String> ans = new ArrayList<>();
    StringBuilder sb = new StringBuilder();

    /*
    review:
    bug1:用hashSet去重复字符串,其实我们要的是每个位置上的字符是否被重复选择;
    bug2:pre当作参数传递,用来去前后字符是否选择重复,其实我们要的是当前位置前后使用是否重复。
    bug3:想用pre去重,那就得前后排序,让相等的字符挨在一起,否则随着pre的改变,还是会让相同位置有重复。
    总结:两处bug都有相同的本质,就是我思考时不够严谨,记忆容易模糊,让编码带上我以为的味道。
    1-逻辑训练太少,逻辑不够严谨!该类题的逻辑训练太少。
    2-抽象能力(看本质/去其糟糠取其精华)还需要提高,因为bug的总结中老是出现 我以为/其实我们要/我们却选择了等字眼。
     */

}

2、set去重

// 不排序剪枝,而是用set对最后的结果去重。
class Permutation2 {
    /*
    如何排列?回溯
    如何去重?为什么重?
    当回溯时,取前一个后后一个放到第i位时,且当前后相同,则会产生一套重复的排列组合;
    通过组合的最终字符,看以前是否有组合过。
     */
    public String[] permutation(String S) {
        arr = S.toCharArray();
        n = arr.length;

        dfs(new int[n]);

        return ans.toArray(new String[0]);
    }

    // bug1:第二个参数用的HashSet pool,想让hashSet去重,那排列里面用于没有相同字符,这里需要对位置去重,采用数组hash。
    private void dfs(int[] fx) {
        if (sb.length() == n) {
            if (!pool.contains(sb.toString())) {
                ans.add(sb.toString());

                pool.add(sb.toString());
            }
            return;
        }
        for (int i = 0; i < n; i++) {
            if (fx[i] != 1) {
                fx[i] = 1;
                sb.append(arr[i]);

                dfs(fx);

                sb.deleteCharAt(sb.length() - 1);
                fx[i] = 0;
            }
        }
    }

    char[] arr;
    int n;
    List<String> ans = new ArrayList<>();
    Set<String> pool = new HashSet<>();
    StringBuilder sb = new StringBuilder();
}

总结

1)逻辑训练太少,逻辑不够严谨!该类题的逻辑训练太少。
2)抽象能力(看本质/去其糟糠取其精华)还需要提高,因为bug的总结中老是出现 我以为/其实我们要/我们却选择了等字眼。
3)分析问题并拆解,如:怎么排列–回溯;怎么去重?为什么会重复?

参考文献

[1] LeetCode 有重复字符的排列组合

你可能感兴趣的:(数据机构与算法,剪枝,回溯,排列组合,去重,Java)