排列组合是组合学最基本的概念。所谓排列,就是指从给定个数的元素中取出指定个数的元素进行排序。组合则是指从给定个数的元素中仅仅取出指定个数的元素,不考虑排序。
详细定义参考:https://baike.baidu.com/item/%E6%8E%92%E5%88%97%E7%BB%84%E5%90%88/706498?fr=aladdin
在各种算法比赛,或面试题中经常会出现关于排列组合的算法题,这里总结几种典型解法来给大家参考
排列
元素不重复的全排列
如果是简单的排列计数问题可以通过数学公式进行计算,但如果题目的是带条件的计数问题,或者排列枚举问题,就可以使用递归加以解决
例:输出0-9的全排列
/**
* 全排列0-9
* @author Administrator
*
*/
public class Main {
static int[] a ={0,1,2,3,4,5,6,7,8,9};
//static int[] b = new int[10];
public static void main(String[] args) {
f(0);
}
static void f(int n){
if(n==a.length-1){//出口
for (int i : a) {
System.out.print(i+" ");
}
System.out.println();
return;
}
for(int i = n ; i < a.length ; i++){//相似性,每一位元素与后面的元素交换位置
{int t = a[n] ; a[n] = a[i] ; a[i] = t;};
f(n+1);
{int t = a[n] ; a[n] = a[i] ; a[i] = t;};
}
}
}
上面的就是典型的元素不重复的全排列问题,当枚举完成的时候同时也完成了计数,可以在出口处添加条件来解决类似剪邮票的问题,先暴力枚举所有情况,然后根据条件过滤结果
元素不重复的部分排列
类似的,元素不重复的部分排列问题也可以由递归解决
/**
* 从3个元素中,取两个元素
* @author Administrator
*
*/
public class Main {
static int[] a ={0,1,3};
//static int[] b = new int[10];
public static void main(String[] args) {
f(0);
}
static void f(int n){
if(n==2){//改变出口处的数字,选择想要的元素的个数
for(int i = 0 ; i < 2 ; i++){
System.out.print(a[i]);
}
System.out.println();
return;
}
for(int i = n ; i < a.length ; i++){
{int t = a[n] ; a[n] = a[i] ; a[i] = t;};
f(n+1);
{int t = a[n] ; a[n] = a[i] ; a[i] = t;};
}
}
}
元素重复的全排列
接下来是有重复元素的排列问题
https://blog.csdn.net/shengsikandan/article/details/52807721
组合
组合计数(元素不重复)
设有m(m!=0)个苹果,先要求取n(n!=0)个有多少种取法
利用公式 f(m,n) = f(m-1,n-1)+f(m-1,n)递归解决
ps:假设苹果s被取出有f(m,n-1),假设苹果s未被取出有f(m-1,n)
/**
* 元素不重复组合计数问题
* @author Administrator
*
*/
public class Main {
public static void main(String[] args) {
System.out.println(f(4,2));
}
static int f(int m,int n){
if(m==0 || n == 0)
return 1;
if(n > m)
return 0;
if(m==n)
return 1;
return f(m-1,n)+f(m-1,n-1);
}
}
组合枚举
元素不重复
当元素不重复且固定的时候,可以用多重循环枚举出来,可当固定时就需要用递归了。
例:从ABCDE选取三个字符
/**
* 元素不重复组合枚举问题
* @author Administrator
*
*/
public class Main {
public static void main(String[] args) {
List<String> l = f("ABCDE",3);
for (String s : l) {
System.out.println(s);
}
}
static List<String> f(String s , int n){
List<String> l = new Vector<String>();
if(n==0){
l.add("");
return l;
}
//相似性
for(int i = 0 ; i < s.length() ; i++){
char c = s.charAt(i);//取元素
List<String> t = f(s.substring(i+1),n-1); //模拟循环不取已经取过的元素
for (String str: t) {
l.add(c+str);
}
}
return l;
}
}
元素重复
例:选取AAABBC中的任意三个字母
思路一:假设字符串不重复,取出所有组合,用set集合去重
思路二:用int数组储存ABC可能存在的最大次数,在递归中将最大可能和要取的数中进行比较,利用递归得到ABC出现的次数所有可能储存到数组中,最后利用work()输出
/**
* 元素重复组合枚举问题
* 选取AAABBC中的任意三个字母
* @author Administrator
*
*/
public class Main {
public static void main(String[] args) {
int[] data = {3,2,1};
int[] x = new int[data.length];
f(data,x,0,3);
}
/**
* @param data : 字母允许出现的最大次数
* @param x : 结果集中字母出现的次数
* @param k : 当前位置
* @param goal : 当前需要取的字母个数
*/
static void f(int[] data , int[] x , int k , int goal){
if(k==data.length){
if(goal==0)
work(x);
return;
}
for(int i = 0 ; i <=Math.min(data[k], goal);i++){//i表示可能的次数
x[k] = i;//不断将i放入数组中试验
f(data,x,k+1,goal-i);
}
x[k] = 0;//回溯
}
/**
*
* @param x : x数组中记录了每一个字母出现的次数
*/
static void work(int[] x) {
for(int i = 0 ; i < x.length ; i++){
for(int j = 0 ; j < x[i] ; j++){
System.out.print((char)('A'+i));//由于元素是连续字母,输出可以简单处理
}
}
System.out.println();
}
}
例题:派遣问题