主要内容:
①排列问题
②组合计数问题
③组合枚举问题
④递归设计
递归的难点在于:相似性的设计,如何设计参数才能相似
最常见的问题:
排列问题=排列计数+排列枚举
组合问题=组合计数+组合枚举
重点是:排列枚举问题
关键点:不重不漏
递归也是暴力解法的一种,相比循环而言,循环是需要确定循环次数的
1. 搭积木
【问题描述】小明最近喜欢搭数字积木。一共有10块积木,每个积木上有一个数字,0~9。
搭积木规则:
每个积木放到其它两个积木的上面,并且一定比下面的两个积木数字小。
最后搭成4层的金字塔形,必须用完所有的积木。
下面是两种合格的搭法:
0
1 2
3 4 5
6 7 8 9
0
3 1
7 5 2
9 8 6 4
请你计算这样的搭法一共有多少种?
①4层塔型是固定的,变化的是数字,可以用一维数组来表示每个位置
②筛选条件类似于:a[0]<a[1]&&a[0]<a[2].....
【源代码】【JAVA:于航】
/*
存储位置备忘
0
1 2
3 4 5
6 7 8 9
*/
public class A
{
static int N;
static void show(int[] a)
{
System.out.println(" " + a[0]);
System.out.println(" " + a[1] + " " + a[2]);
System.out.println(" " + a[3] + " " + a[4] + " " + a[5]);
System.out.println("" + a[6] + " " + a[7] + " " + a[8] + " " + a[9]);
System.out.println();
}
static boolean near(int a, int b)
{
if(a+1==b || a==b+1) return true;
return false;
}
static void test(int[] a)
{
if(a[1]<a[0]) return;
if(a[2]<a[0]) return;
if(a[3]<a[1]) return;
if(a[4]<a[1]) return;
if(a[4]<a[2]) return;
if(a[5]<a[2]) return;
if(a[6]<a[3]) return;
if(a[7]<a[3]) return;
if(a[7]<a[4]) return;
if(a[8]<a[4]) return;
if(a[8]<a[5]) return;
if(a[9]<a[5]) return;
show(a);
N++;
}
// a: 待排元素
// k: 当前考虑的位置
static void f(int[] a, int k)
{
if(k==a.length-1){
test(a);
return;
}
for(int i=k; i<a.length; i++){
{int t=a[i]; a[i]=a[k]; a[k]=t;}
f(a,k+1);
{int t=a[i]; a[i]=a[k]; a[k]=t;}
}
}
public static void main(String[] args)
{
int[] a = {0,1,2,3,4,5,6,7,8,9};
f(a,0);
System.out.println("N= " + N);
}
}
2. 代表团出访//此题原题为代码填空题
【问题描述】X星球要派出一个5人组成的观察团前往W星。
其中:
A国最多可以派出4人。
B国最多可以派出2人。
C国最多可以派出2人。
D国最多可以派出1人。
E国最多可以派出1人。
F国最多可以派出3人。
那么最终派往W星的观察团会有多少种国别的不同组合呢?
有重复的组合枚举问题:
①每次交换后面的元素,怎么能够保证不换来重复的?
②与自己不相等才换?
【JAVA:于航】
public class A
{
//a:可取最大个数的限定
//k: 当前考虑位置
//n: 目标名额
//s: 已经决定的代表团成员
public static void f(int[] a, int k, int n, String s)
{
if(k==a.length){
if(n==0) System.out.println(s);
return;
}
String s2 = s;//被分配的时候,其实是一个新的串,因此省去了回溯的步骤
for(int i=0; i<=a[k]; i++){
f(a,k+1,n-i,s2);
s2 += (char)(k+'A');
}
}
public static void main(String[] args)
{
int[] a = {4,2,2,1,1,3};
f(a,0,5,"");
}
}
3. 排列枚举
【问题描述】已知不同字母构成的串,求它的全排列
【源代码】
【JAVA:于航】
import java.util.*;
public class A
{
static List f(String s){
List lst = new Vector();
//出口条件
if(s.length()==1){
lst.add(s);
return lst;
}
for(int i=0; i<s.length(); i++){
char x = s.charAt(i);
List t = f(s.substring(0,i)+s.substring(i+1));
for(int k=0; k<t.size(); k++){
lst.add("" + x + t.get(k));
}
}
return lst;
}
public static void main(String[] args){
List lst = f("ABC");
for(int i=0; i<lst.size(); i++){
System.out.println(lst.get(i));
}
}
}
// ABCDE 所有排列
public class B
{
// aa: 待排数据
// k: 考虑的当前位置(数组下标)
//递归:通常是从某一点出发考虑问题,再把问题交给相似的问题
此即 典型问题的递归框架
static void f(char[] aa, int k){if(k==aa.length-1){//达到倒数第一个元素,不用交换
System.out.println(String.valueOf(aa));
return;//已达到上限
}
for(int i=k; i<aa.length; i++){//i=k:即自己与自己交换,也是一种,不能忽略
{char t=aa[k]; aa[k]=aa[i]; aa[i]=t;} // 试探(将每一个元素都与第k个元素进行交换)
f(aa,k+1);//相似性的设置
{char t=aa[k]; aa[k]=aa[i]; aa[i]=t;} // 回溯
}
}
public static void main(String[] args){
f("ABC".toCharArray(), 0);
}
}
4. 组合
【问题描述】有重复的字母中求取出m个所有组合
例如: “AAABBCCCCCCDD” 中取3个字母的所有组合
【源代码】
【JAVA:于航】
A:组合计数问题
public class A
{
// m个不同的球中,取n个
static int f(int m, int n){
if(n==m) return 1;
if(n==0) return 1;//两个球中一个都不取,也是一种取法
return f(m-1,n) + f(m-1,n-1);
//假设第m个球是特殊的,有两种情况:①第m个球一定取出来了②不取出m球
}
public static void main(String[] args){
System.out.println(f(5,3));
System.out.println(f(5,2));
}
}
B:组合枚举问题(循环暴力解法,有局限)
// 固定数目的组合问题
// ABCDE 中取3个
public class B
{
public static void main(String[] args){
for(char i='A'; i<='E'; i++){
//只能从上层循环的后面一个开始取,保证不重复
for(char k= (char)(j+1); k<='E'; k++){
System.out.println(""+i+j+k);
}
}
}
}
}
import java.util.*;
// 递归思路:第1次取什么?
C:组合枚举问题(递归解法)
f(串=“ABCDE”,取数=3){
创建空列表LL “A”拿出来
f("BCDE",2) =>列表L1 “A”组合L1入L2
“B”拿出来
f("ACDE",2) =>列表L2 “B”组合L2入LL
。。。。。。
}
public class C{
static List f(String s, int n){
List lst = new Vector();
if(n==0){
lst.add("");
return lst;
}
for(int i=0; i<s.length(); i++){
char x = s.charAt(i);
List t = f(s.substring(i+1),n-1);
for(int k=0; k<t.size(); k++){
lst.add("" + x + t.get(k));
}
}
return lst;
}
public static void main(String[] args){
List lst = f("ABCDE", 3);
for(int i=0; i<lst.size(); i++){
System.out.println(lst.get(i));
}
}
}
还有另外一种取法:可以适应更一般的情况(具有重复元素)
// "AABBBC" 取3个, 哪些取法?
public class D
{
static void work(int[] x){
for(int i=0; i<x.length; i++){
for(int k=0; k<x[i]; k++){
System.out.print((char)('A'+i));
}
}
System.out.println();
}
// data: 不动, 限制条件
// x: 取法
// k: 当前考虑的位置(当前考虑某一点,其余的交给下一层去递归)
// goal: 距离目标的剩余名额
static void f(int[] data, int[] x, int k, int goal){
if(k==x.length){
if(goal==0) work(x);
return;
}
for(int i=0; i<=Math.min(data[k],goal); i++){
x[k] = i;
f(data, x, k+1, goal-i);
}
x[k] = 0; // 回溯
}
public static void main(String[] args)
{
int[] data = {2,3,1}; // 每个元素的最大个数
int[] x = new int[data.length]; // 每个元素取几个
f(data, x, 0, 3);
}
}
5. 扑克序列
【问题描述】A A 2 2 3 3 4 4, 一共4对扑克牌。请你把它们排成一行。
要求:两个A中间有1张牌,两个2之间有2张牌,两个3之间有3张牌,两个4之间有4张牌。
请填写出所有符合要求的排列中,字典序最小的那个。
例如:22AA3344 比 A2A23344 字典序小。当然,它们都不是满足要求的答案。
【源代码】
【JAVA:于航】
import java.util.*;
public class TA
{
static Set set = new TreeSet();
static void test(char[] da)
{
String s = new String(da);
if(s.lastIndexOf('A') - s.indexOf('A') != 2) return;
if(s.lastIndexOf('2') - s.indexOf('2') != 3) return;
if(s.lastIndexOf('3') - s.indexOf('3') != 4) return;
if(s.lastIndexOf('4') - s.indexOf('4') != 5) return;
set.add(s);
}
static void f(char[] da, int k)
{
if(k==da.length){//方法一
test(da);
return;
}
//方法二
String his=" ";//存放历史记录
for(int i=k; i<da.length; i++){
if(his.indexOf(data[i])>=0) continue;
his+=da[i];
f(da,k+1);
{char t=da[k]; da[k]=da[i]; da[i]=t;}
}
}
public static void main(String[] args)
{
char[] da = "AA223344".toCharArray();
f(da, 0);
for(Object s: set) System.out.println(s);
}
}