剑指Offer_24 字符串的排序
2018/6/19 星期二
题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
牛客网代码编写框架:
import java.util.ArrayList;
public class Solution {
public ArrayList<String> Permutation(String str) {
}
}
这个问题就是可转换为,“a”、”b”、”c“三个字符中找出其中所有的排列问题。这就是典型的「全排列」。学过排列组合的我们知道,总的排列可能次数就是 种可能的情况。
这里我们从迭代的思想中进行考虑。我们将 abc
中分解为两个部分,第一个字符和剩余字符部分。如果第一个字符为 a
,那么就是求解剩余的 bc
两个字符的全排列。同理,再依次的求结果首字母为 b
和 c
的情况。这就是一个典型的迭代过程。
解题思路如下:
- 从n 个字符中选出任意一个字符
- 从剩余的 n-1个字符中任选一个字符
- 依次的选择,直到最后。。。
在下面的 Java 代码中,我们每次将输入字符串转换成字符串数组(char[] chars),方便我们进行字符的个数统计和「选取」。如何从数组中,区分「排列好的前缀」和后面「待排的元素」?我们可以使用一个数字 offset 来标记数组中,数组的前缀和后面待排元素的分界线。当 offset 移动到整个数组的后面时候(也就是 offset == 数组的长度),就开始输出序列。
import java.util.ArrayList;
import java.util.Collections;
/**
* 字符串的排列与组合
* <p>一个很经典的问题,就是给你,a,b,c是三个字符串,输出,它所有可能的排列,比如,abc,acb,bac等。
* 根据排列组合我们知道,总的排列个数是 C_3^3 = 6,如何用编程来实现打印所有字符的需求呢?21 </p>
*
* @author jhZhang
* @date 2018/6/15
*/
public class Solution {
/**
* 把字符串拆分成两部分,第一部分不变,第二部分开始进行交换
*
* @param str
*/
public ArrayList<String> Permutation(String str) {
ArrayList<String> result = new ArrayList<>();
if (str == null || str.length() <= 0) {
return result;
}
char[] chars = str.toCharArray();
Premutation(chars, 0, result);
Collections.sort(result);
return result;
}
private void Premutation(char[] chars, int offset, ArrayList<String> result) {
int len = chars.length;
if (offset < 0 || offset > len) {
return;
}
if (offset == len) {
String str = String.valueOf(chars);
if (!result.contains(str)) {
result.add(String.valueOf(chars));
}
}
for (int i = offset; i < len; i++) {
swap(chars, offset, i);
// 保存第offset元素,进行交换
Premutation(chars, offset + 1, result);
swap(chars, offset, i);
}
}
public void swap(char[] chars, int i, int j) {
char tmp = chars[j];
chars[j] = chars[i];
chars[i] = tmp;
}
}
上面的这段代码是可以直接通过练习,但是在这里也遇到了几个问题。
- 提示我,当输入情况为null时,返回的情况不对,[] 而不是 null。指针情况不对,我是直接返回null,而题目要求应该是先新建个ArrayList 如果为null,则直接返回该新建的list。
- 一开始不清楚,String.valueOf() 中输入 char[] 对象时,会直接的返回char数组组合出来的字符串。
- 需要考虑字符串中输入重复的情况。
- 需要考虑添加的列表进行排序。
测试用例
- 功能测试:输入的字符串中有一个或者多个字符
- 特殊输入测试:输入的字符串中内容为空或者为Null指针。
本题扩展-如何求出字符串的组合
如果题目要求的不是求字符串的所有排列,而是求字符的所有「组合」(假设所有的字符都不重复),应该怎么办呢?
同样的从一个具体的例子中进行说明,如果我们输入的候选字符格式是,“a”、”b“、”c“ 那么这三个字符存在多少种组合呢?
如果输出结果有一个字符,那么存在,{a,b,c}, 种情况;
如果输出结果有两个字符,那么存在,{ab,ac,bc}, 种情况;
如果输出结果有三个字符,那么存在,{abc}, 种情况。
所以,我们知道,解决上诉组合问题的关键就在于如何计算实现 的求法。 表示的意义就是从n个字符中取出其中的m个字符。
如何更好的进行编程呢?如何求三个字符的「组合」,我们将整个过程在分解一下。
n 个字符的「组合」,可以分解成求 ,所以重点难点在于如何求解出 的组合(从 n 个字符中 取出 m 个字符的组合)。
同样的,我们知道
。这个公式所表达的实际意义就可以描述为,从
的排列中,剔除字符串的顺序影响。这是我个人的理解,实际上就是将,abc
和 bac
中只保留任意一个。所以,计算组合的时候,我们仍然可以直接的以按照计算「排列」的情况来计算,但在最后的时候,剔除掉这些元素相同的「组合」。
如何求解 的元素组合? 这就是一个典型的从 n 个数字中取出其中的 m个数字。
- 我们分解一下, 。我们首先从 n 中任取出一个字符,存入「容器」(该容器表示选中需要输出的m个元素集合);
- 然后,从取出字符后的剩余数组中,再任取一个字符;
- 依次上述的过程……
- 当我们取出字符个数达到 m 时候,就是输出我们当中的一个组合。
- 最后,对存入的「容器」去重。
我们这里,选择「容器」为HashSet
,因为本身set
集合存储字符串与元素添加顺序无关,且自带去重属性。
package offer.jianzhi.chapter4;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
/**
* @author jhZhang
* @date 2018/6/17
*/
public class Main24Combine {
public ArrayList<String> combine(String str) {
ArrayList<String> result = new ArrayList<String>();
if (str == null && str.isEmpty()) {
return result;
}
// 组合 C_n^1+C_n^2 + …… + C_n^n
for (int i = 1; i <= str.length(); i++) {
combine(str.toCharArray(), new HashSet(i), i, result);
}
Collections.sort(result);
return result;
}
/**
* 计算 C_n^m 的组合次数
*
* @param pres 选中的元素
* @param m 选中的个数
* @param result 返回的结果
*/
private void combine(char[] chars, HashSet pres, int m, ArrayList<String> result) {
if (pres.size() == m) {
// 将容器转换为字符串
String stack = String.valueOf(pres);
// 去除重复元素
if (!result.contains(stack)) {
System.out.println(stack);
result.add(stack);
}
return;
}
for (int i = 0, len = chars.length; i < len; i++) {
swap(chars, 0, i);
char[] newChars = new char[len - 1];
// 复制未选中的剩余元素
System.arraycopy(chars, 1, newChars, 0, len - 1);
pres.add(chars[0]);
combine(newChars, pres, m, result);
// 回退一格选中的元素
pres.remove(chars[0]);
swap(chars, 0, i);
}
}
public void swap(char[] chars, int i, int j) {
char tmp = chars[j];
chars[j] = chars[i];
chars[i] = tmp;
}
public static void main(String[] args) {
String testStr = "1234";
new Main24Combine().combine(testStr);
}
}
最终输出的结果如下:
[1]
[2]
[3]
[4]
[1, 2]
[1, 3]
[4, 1]
[2, 3]
[4, 2]
[4, 3]
[1, 2, 3]
[4, 1, 2]
[4, 1, 3]
[4, 2, 3]
[1, 2, 3, 4]
如果觉得文章存在问题,欢迎留言批评指正,而且我觉得实现的内容应该还是可以再优化的。如果觉得不错,加一波关注共同学习交流。