递归解决排列组合问题

排列组合是组合学最基本的概念。所谓排列,就是指从给定个数的元素中取出指定个数的元素进行排序。组合则是指从给定个数的元素中仅仅取出指定个数的元素,不考虑排序。

详细定义参考: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();
	}
}

例题:派遣问题



猜你喜欢

转载自blog.csdn.net/betonme/article/details/79666528