解锁大厂面试:字符串全排列的Java实现与优化

在这里插入图片描述

前言

🚀 博主介绍:大家好,我是无休居士!一枚任职于一线Top3互联网大厂的Java开发工程师! 🚀

🌟 欢迎大家关注我的微信公众号【JavaPersons】!在这里,你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人,我不仅热衷于探索一些框架源码和算法技巧奥秘,还乐于分享这些宝贵的知识和经验。

💡 无论你是刚刚踏入编程世界的新人,还是希望进一步提升自己的资深开发者,在这里都能找到适合你的内容。我们共同探讨技术难题,一起进步,携手度过互联网行业的每一个挑战

📣 如果你觉得我的文章对你有帮助,请不要吝啬你的点赞👍分享💕和评论哦! 让我们一起打造一个充满正能量的技术社区吧!



1. 引言 📘

在互联网大厂的算法面试中,字符串全排列是一个经典且常见的问题。特别是当字符串中包含重复字符时,问题变得更加复杂。本文将详细讲解如何使用Java来解决这个问题。

2. 分析题意 🧐

题目要求输入一个长度为 n 的字符串,打印出该字符串中字符的所有排列。字符串中的字符可以重复。例如,输入字符串 AAB,则输出所有可能的排列组合,如 AAB, ABA, BAA

2.1示例

  • 输入: s = "AAB"
  • 输出: ["AAB", "ABA", "BAA"]

2.2注意事项

  • 输出可以以任意顺序返回。
  • 字符串中的字符可以重复。

3. 考察知识点 📚

  1. 递归与回溯:理解递归的基本概念和回溯算法的应用。
  2. 字典序生成:了解如何通过字典序生成排列。
  3. 数据结构:熟练使用数组、列表等数据结构。
  4. 算法复杂度分析:掌握时间复杂度和空间复杂度的分析方法。
  5. 去重技巧:处理重复字符的去重问题。

4. 解题思路

4.1 回溯法 (Backtracking)

回溯法是一种通过递归来解决问题的方法,它尝试每一种可能的情况,直到找到解决方案或确定没有解决方案为止。对于字符串全排列问题,我们可以使用回溯法来生成所有的排列组合,并通过一些技巧来处理重复字符。

4.1.1 步骤

  1. 初始化:定义一个结果列表 result 来存储所有的排列组合。
  2. 递归函数:定义一个递归函数 backtrack,参数包括当前路径 path 和剩余字符 remainingChars
  3. 终止条件:如果 remainingChars 为空,则将 path 添加到 result 中。
  4. 选择与撤销选择:遍历 remainingChars 中的每一个字符,将其添加到 path 中,然后递归调用 backtrack。递归返回后,从 path 中移除该字符,继续尝试下一个字符。
  5. 去重:为了避免重复排列,在选择字符时,跳过已经选择过的相同字符。

4.1.2 代码示例

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class StringPermutations {
    public List<String> permute(String s) {
        List<String> result = new ArrayList<>();
        char[] chars = s.toCharArray();
        Arrays.sort(chars); // 排序以便于去重
        backtrack(new StringBuilder(), chars, new boolean[chars.length], result);
        return result;
    }

    private void backtrack(StringBuilder path, char[] chars, boolean[] used, List<String> result) {
        if (path.length() == chars.length) {
            result.add(path.toString());
            return;
        }

        for (int i = 0; i < chars.length; i++) {
            if (used[i]) continue; // 已经使用过的字符跳过
            if (i > 0 && chars[i] == chars[i - 1] && !used[i - 1]) continue; // 去重

            // 选择
            path.append(chars[i]);
            used[i] = true;

            // 递归
            backtrack(path, chars, used, result);

            // 撤销选择
            path.deleteCharAt(path.length() - 1);
            used[i] = false;
        }
    }

    public static void main(String[] args) {
        StringPermutations solver = new StringPermutations();
        List<String> permutations = solver.permute("AAB");
        System.out.println(permutations);
    }
}

4.1.3 时间复杂度和空间复杂度

  • 时间复杂度:O(n * n!),其中 n 是字符串的长度。每个字符都要参与 n! 种排列。
  • 空间复杂度:O(n),递归栈的深度最多为 n。

4.2 字典序生成法 (Lexicographic Permutations)

字典序生成法利用了字典序的特性来生成排列。这种方法不需要额外的空间来存储中间结果,因此在空间复杂度上有优势。为了处理重复字符,我们需要在生成排列的过程中进行去重。

4.2.1步骤

  1. 排序:首先将字符串按字典序排序。
  2. 生成排列:从右向左找到第一个比右边小的字符 i,然后从右向左找到第一个比 i 大的字符 j,交换 ij,最后反转 i 右边的所有字符。
  3. 去重:在每次交换字符时,检查是否已经生成过相同的排列。
  4. 循环:重复上述步骤,直到无法再生成新的排列。

4.2.2代码示例

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class StringPermutations {
    public List<String> permute(String s) {
        List<String> result = new ArrayList<>();
        char[] chars = s.toCharArray();
        Arrays.sort(chars); // 初始化为最小字典序
        result.add(new String(chars));

        while (true) {
            int i = chars.length - 2;
            while (i >= 0 && chars[i] >= chars[i + 1]) {
                i--;
            }
            if (i < 0) break; // 无法生成新的排列

            int j = chars.length - 1;
            while (chars[j] <= chars[i]) {
                j--;
            }
            swap(chars, i, j);
            reverse(chars, i + 1);
            
            // 去重
            if (!result.contains(new String(chars))) {
                result.add(new String(chars));
            }
        }

        return result;
    }

    private void swap(char[] chars, int i, int j) {
        char temp = chars[i];
        chars[i] = chars[j];
        chars[j] = temp;
    }

    private void reverse(char[] chars, int start) {
        int end = chars.length - 1;
        while (start < end) {
            swap(chars, start, end);
            start++;
            end--;
        }
    }

    public static void main(String[] args) {
        StringPermutations solver = new StringPermutations();
        List<String> permutations = solver.permute("AAB");
        System.out.println(permutations);
    }
}

4.2.3 时间复杂度和空间复杂度

  • 时间复杂度:O(n * n!),每次生成一个新的排列需要 O(n) 的时间。
  • 空间复杂度:O(1),除了结果列表外,不需要额外的空间。

5. 总结与建议 🎓

通过上述两种方法,我们解决了字符串全排列的问题,特别是当字符串中包含重复字符时。回溯法直观易懂,但递归可能会导致较大的空间开销;字典序生成法则更加高效,适用于大规模数据处理。

5.1建议

  1. 理解递归与回溯:多练习递归和回溯相关的题目,加深理解。
  2. 掌握字典序生成法:这是一种非常实用的技术,适用于很多排列组合问题。
  3. 去重技巧:在处理重复字符时,合理使用排序和标记技术来避免重复排列。
  4. 优化技巧:在实际应用中,可以根据具体需求选择合适的算法,并进行相应的优化。

希望这篇文章能帮助你在面试中脱颖而出!加油!💪


如果你有任何问题或需要进一步的帮助,请随时联系我!祝你面试顺利!🎉

乐于分享和输出干货的Java技术公众号:JavaPersons

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休居士

感谢您的支持,我会继续努!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值