面试题 08.08. 有重复字符串的排列组合

有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。

示例 1:

 输入:S = "qqe"
 输出:["eqq","qeq","qqe"]

示例 2:

 输入:S = "ab"
 输出:["ab", "ba"]
class Solution {
    public String[] permutation(String S) {
        Set<String> set = new HashSet<>();
        int len = S.length();
        List<Character> list = new ArrayList<>();
        for (int i = 0; i < len; i++) {
            list.add(S.charAt(i));
        }
        backtrack(list, new StringBuilder(len), set);
        return set.toArray(new String[0]);
    }

    private void backtrack(List<Character> list, StringBuilder sb, Set<String> set) {
        if (list.isEmpty()) {
            set.add(sb.toString());
            return;
        }
        for (int i = 0; i < list.size(); i++) {
            char ch = list.remove(i);
            sb.append(ch);
            backtrack(list, sb, set);
            sb.delete(sb.length()-1, sb.length());
            list.add(i, ch);
        }
    }
}
  1. 创建HashSet来存储结果,利用Set的特性自动去重

  2. 将输入字符串转换为ArrayList<Character>,方便后续操作

  3. 调用backtrack方法进行回溯生成所有排列

  4. 最后将Set转换为String数组返回

  1. 基线条件:当list为空时,说明已经生成了一个完整的排列,将其加入结果集

  2. 循环处理:遍历当前可用的所有字符

    • 取出当前字符list.remove(i)

    • 将字符加入当前排列sb.append(ch)

    • 递归处理剩余字符backtrack(list, sb, set)

    • 回溯操作

      • 从排列中移除最后添加的字符sb.delete()

      • 将字符放回原位置list.add(i, ch)

举例说明(输入 S = "aab"

初始状态
  • list = ['a', 'a', 'b'](可选的字符)

  • sb = ""(当前已选的字符)

  • set = [](结果集)

递归过程(逐层展开)
  1. 第一层递归(i=0)

    • 选择 list[0]='a'

      • list 移除 'a' → ['a', 'b']

      • sb 追加 'a' → "a"

      • 进入递归:

        • 第二层递归(i=0)

          • 选择 list[0]='a'

            • list 移除 'a' → ['b']

            • sb 追加 'a' → "aa"

            • 进入递归:

              • 第三层递归(i=0)

                • 选择 list[0]='b'

                  • list 移除 'b' → []

                  • sb 追加 'b' → "aab"

                  • 终止条件list 为空,set = ["aab"]

                  • 回溯:

                    • sb 删除 'b' → "aa"

                    • list 插回 'b' → ['b']

              • 回溯:

                • sb 删除 'a' → "a"

                • list 插回 'a' → ['a', 'b']

        • 第二层递归(i=1)

          • 选择 list[1]='b'

            • list 移除 'b' → ['a']

            • sb 追加 'b' → "ab"

            • 进入递归:

              • 第三层递归(i=0)

                • 选择 list[0]='a'

                  • list 移除 'a' → []

                  • sb 追加 'a' → "aba"

                  • 终止条件set = ["aab", "aba"]

                  • 回溯:

                    • sb 删除 'a' → "ab"

                    • list 插回 'a' → ['a']

            • 回溯:

              • sb 删除 'b' → "a"

              • list 插回 'b' → ['a', 'b']

      • 回溯:

        • sb 删除 'a' → ""

        • list 插回 'a' → ['a', 'a', 'b']

  2. 第一层递归(i=1)

    • 选择 list[1]='a'(与 i=0 的 'a' 相同,但位置不同):

      • 过程类似,最终生成 "aab"(重复,TreeSet 会自动去重)。

  3. 第一层递归(i=2)

    • 选择 list[2]='b'

      • list 移除 'b' → ['a', 'a']

      • sb 追加 'b' → "b"

      • 进入递归:

        • 第二层递归(i=0)

          • 选择 'a' → "ba" → 递归 → 选择 'a' → "baa"(加入 set)。

        • 第二层递归(i=1)

          • 选择另一个 'a' → "ba" → 递归 → 选择 'a' → "baa"(重复)。

最终结果

set = ["aab", "aba", "baa"](自动去重且按字典序排序)。


关键点

  1. 去重机制

    • TreeSet 保证结果唯一且有序。

    • 即使两个 'a' 相同,它们的排列仍会被生成,但重复结果会被 Set 过滤。

  2. 回溯的核心操作

    • 选择list.remove(i) + sb.append(ch)

    • 撤销sb.deleteLastChar() + list.add(i, ch)

    • 确保每一层递归结束后,list 和 sb 恢复原状,才能继续尝试其他分支。

  3. 递归树示意图(以 "aab" 为例):

    Start: list=[a,a,b], sb=""
    |
    ├─ 选a → list=[a,b], sb="a"
    │   ├─ 选a → list=[b], sb="aa"
    │   │   └─ 选b → sb="aab" ✅
    │   └─ 选b → list=[a], sb="ab"
    │       └─ 选a → sb="aba" ✅
    └─ 选b → list=[a,a], sb="b"
        └─ 选a → list=[a], sb="ba"
            └─ 选a → sb="baa" ✅

通过这种逐步选择、递归、回溯的方式,代码能高效生成所有唯一排列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值