算法的学习笔记—字符串的排列(牛客JZ38)
😀前言
在编程面试中,字符串排列问题是一类常见但又具有挑战性的题目。本文将通过一道经典的字符串排列题目,带领你一步步深入分析并最终解决这一问题。
🥰字符串的排列
😋问题描述
给定一个字符串,要求输出该字符串中字符的所有排列组合。字符串中字符的排列顺序可以是任意的,但每个排列都必须是字符串中的字符的唯一组合。
例如,输入字符串"ABC"
,则输出的所有排列组合应包括 "ABC", "ACB", "BAC", "BCA", "CAB", 和 "CBA"
。
注: 字符串的长度不超过10
,字符只包括大小写字母。
数据范围:n<10
要求:空间复杂度 O(n!),时间复杂度 O(n!)
😊示例解析
-
示例1:
输入:“ab”
返回值:[“ab”,“ba”]
-
示例2:
输入:“aab”
返回值:[“aab”,“aba”,“baa”]
-
示例3:
输入:“abc”
返回值:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]
-
示例4:
输入:""
返回值:[""]
在以上示例中,字符串的排列组合均满足唯一性要求,并且结果顺序可以是任意的。
❤️🔥解题思路
这个问题可以归类为典型的回溯算法问题。回溯算法是一种通过探索所有可能的解决方案并回溯以避免无效路径的策略。在这里,我们需要生成所有字符的排列组合,并在生成过程中避免重复排列。
步骤概述:
- 排序字符串: 为了在生成排列时去除重复,我们首先对字符串中的字符进行排序。
- 回溯法生成排列: 利用回溯法逐一生成所有可能的字符排列,并在每个字符位置选择后进行递归调用。当遍历到字符串末尾时,将该排列加入结果集中。
- 去重: 在生成排列的过程中,如果当前字符与前一个字符相同且前一个字符未使用过,则跳过该字符,避免生成重复排列。
😊代码实现
下面是一段基于上述思路的Java代码实现:
import java.util.ArrayList;
import java.util.Arrays;
public class StringPermutation {
// 定义一个全局变量 ret 来存储所有的排列结果
private ArrayList<String> ret = new ArrayList<>();
public ArrayList<String> Permutation(String str) {
// 如果输入字符串为空,直接返回空结果
if (str.length() == 0) {
return ret;
}
// 将字符串转换为字符数组,并对其排序
char[] chars = str.toCharArray();
Arrays.sort(chars);
// 调用回溯算法,生成所有排列
backtracking(chars, new boolean[chars.length], new StringBuilder());
// 返回所有的排列结果
return ret;
}
private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) {
// 递归基准条件:如果当前排列的长度与字符数组长度相同,说明找到一个完整排列
if (s.length() == chars.length) {
// 将排列结果添加到全局列表 ret 中
ret.add(s.toString());
return;
}
// 遍历字符数组,尝试每一个字符
for (int i = 0; i < chars.length; i++) {
// 如果当前字符已经被使用过,跳过该字符
if (hasUsed[i]) {
continue;
}
// 如果当前字符与前一个字符相同,且前一个字符没有被使用过,跳过该字符
// 这是为了避免生成重复的排列
if (i != 0 && chars[i] == chars[i - 1] && !hasUsed[i - 1]) {
continue;
}
// 标记当前字符为已使用
hasUsed[i] = true;
// 将当前字符添加到排列中
s.append(chars[i]);
// 递归调用,继续生成剩余字符的排列
backtracking(chars, hasUsed, s);
// 回溯过程:移除最后添加的字符,并将其标记为未使用
s.deleteCharAt(s.length() - 1);
hasUsed[i] = false;
}
}
}
😊代码详解
- 递归基准条件:在
backtracking
方法中,当构造的字符串s
的长度与原字符串长度相同时,说明找到了一个有效的排列,将其加入ret
列表。 - 去重处理:在排列过程中,为避免重复排列的出现,首先对字符数组进行排序,然后在递归时跳过重复的字符(
if (i != 0 && chars[i] == chars[i - 1] && !hasUsed[i - 1])
)。 - 回溯过程:通过使用一个
boolean
数组hasUsed
来记录每个字符是否已被使用。在递归调用后,撤销当前选择并继续尝试其他可能性(s.deleteCharAt(s.length() - 1); hasUsed[i] = false;
)。
😄总结
回溯算法是解决排列问题的有效工具。通过合理的剪枝策略,我们能够在生成所有排列的同时避免不必要的重复计算,从而提高算法的效率。这种方法不仅适用于字符串排列问题,还可以扩展到其他类似的组合问题中。在实际应用中,理解回溯算法的基本思想并灵活运用,将极大地提升你的算法设计能力。
- 点赞
- 收藏
- 关注作者
评论(0)