2018/6/19 星期二
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
牛客网代码编写框架:
import java.util.ArrayList;
public class Solution {
public ArrayList Permutation(String str) {
}
}
这个问题就是可转换为,“a”、”b”、”c“三个字符中找出其中所有的排列问题。这就是典型的「全排列」。学过排列组合的我们知道,总的排列可能次数就是 C33=6 C 3 3 = 6 种可能的情况。
这里我们从迭代的思想中进行考虑。我们将 abc
中分解为两个部分,第一个字符和剩余字符部分。如果第一个字符为 a
,那么就是求解剩余的 bc
两个字符的全排列。同理,再依次的求结果首字母为 b
和 c
的情况。这就是一个典型的迭代过程。
Ann=n×(n−1)×⋯×1 A n n = n × ( n − 1 ) × ⋯ × 1 解题思路如下:
在下面的 Java 代码中,我们每次将输入字符串转换成字符串数组(char[] chars),方便我们进行字符的个数统计和「选取」。如何从数组中,区分「排列好的前缀」和后面「待排的元素」?我们可以使用一个数字 offset 来标记数组中,数组的前缀和后面待排元素的分界线。当 offset 移动到整个数组的后面时候(也就是 offset == 数组的长度),就开始输出序列。
import java.util.ArrayList;
import java.util.Collections;
/**
* 字符串的排列与组合
* 一个很经典的问题,就是给你,a,b,c是三个字符串,输出,它所有可能的排列,比如,abc,acb,bac等。
* 根据排列组合我们知道,总的排列个数是 C_3^3 = 6,如何用编程来实现打印所有字符的需求呢?21
*
* @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;
}
}
上面的这段代码是可以直接通过练习,但是在这里也遇到了几个问题。
如果题目要求的不是求字符串的所有排列,而是求字符的所有「组合」(假设所有的字符都不重复),应该怎么办呢?
同样的从一个具体的例子中进行说明,如果我们输入的候选字符格式是,“a”、”b“、”c“ 那么这三个字符存在多少种组合呢?
如果输出结果有一个字符,那么存在,{a,b,c}, C13=3 C 3 1 = 3 种情况;
如果输出结果有两个字符,那么存在,{ab,ac,bc}, C23=3 C 3 2 = 3 种情况;
如果输出结果有三个字符,那么存在,{abc}, C33=1 C 3 3 = 1 种情况。
所以,我们知道,解决上诉组合问题的关键就在于如何计算实现 Cmn C n m 的求法。 Cmn C n m 表示的意义就是从n个字符中取出其中的m个字符。
如何更好的进行编程呢?如何求三个字符的「组合」,我们将整个过程在分解一下。
n 个字符的「组合」,可以分解成求 C1n+C2n+…Cmn+⋯+Cnn0≤m≤n C n 1 + C n 2 + … C n m + ⋯ + C n n 0 ≤ m ≤ n ,所以重点难点在于如何求解出 Cmn C n m 的组合(从 n 个字符中 取出 m 个字符的组合)。
同样的,我们知道 Cmn=Amnm! C n m = A n m m ! 。这个公式所表达的实际意义就可以描述为,从 Amn A n m 的排列中,剔除字符串的顺序影响。这是我个人的理解,实际上就是将,abc
和 bac
中只保留任意一个。所以,计算组合的时候,我们仍然可以直接的以按照计算「排列」的情况来计算,但在最后的时候,剔除掉这些元素相同的「组合」。
如何求解 Cmn C n m 的元素组合? 这就是一个典型的从 n 个数字中取出其中的 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 combine(String str) {
ArrayList result = new ArrayList();
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 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]
如果觉得文章存在问题,欢迎留言批评指正,而且我觉得实现的内容应该还是可以再优化的。如果觉得不错,加一波关注共同学习交流。