要点:
- 1、相似性:找到相同的部分,将问题一分为二,每一部分可以容易算出或转换成规模小于原问题的类似问题;
- 2、确定递归出口。
案例一:从n个球中取m个(组合问题)
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Scanner;
public class Main {
// 组合问题:从n个球中取m个
// 方法:通过递归枚举
public static void main(String[] args) {
int n = 10, m = 3;
int y = 0;
y = f(n, m);
System.out.println(y);
}
private static int f(int n, int m) {
//出口
if(m==0||n==m) return 1;
//其中一个球取+该球不取
return f(n-1,m-1)+f(n-1,m);
}
}
案例二:字符串有多少种不同的排列方式(排列问题)
思想:把每个元素都与第一个元素交换(也就是说第一个位置确定为某个元素),剩下的部分进行全排列。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Scanner;
public class Main {
// 排列问题
public static void main(String[] args) {
char[] data = "ABC".toCharArray();//转换成字符数组
f(data,0);
}
// k:表示要交换的当前位置
private static void f(char[] data,int k) {
if(k==data.length) {
for (int i = 0; i < data.length; i++) {
System.out.print(data[i]+" ");
}
System.out.println();
}
// 循环条件中蕴含了递归出口
for(int i=k;i<data.length;i++) {
// 交换
{char t=data[k];data[k]=data[i];data[i]=t;}
f(data,k+1);
// 回溯(换回来,保持)
{char t=data[k];data[k]=data[i];data[i]=t;}
}
}
}
案例三:求最大公共子序列长度
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Scanner;
public class Main {
// 求最大公共子序列长度
public static void main(String[] args) {
String a = "abcd";
String b = "acdba";
int y = f(a, b);
System.out.println(y);
}
private static int f(String a, String b) {
// 出口
if(a.length()==0||b.length()==0) return 0;
// 两个串的第一个字符相同
if(a.charAt(0)==b.charAt(0)) {
return 1+f(a.substring(1),b.substring(1));
}
// 两个串的第一个字符不相同
else {
return Math.max(f(a.substring(1),b.substring(0)), f(a.substring(0),b.substring(1)));
}
}
}
案例四:字符出串反转
public class Main {
// 字符出串反转
public static void main(String[] args) {
String a = "abcde";
System.out.println(f(a));
}
private static String f(String a) {
if(a.length()==1) return a.charAt(0)+"";
return f(a.substring(1))+a.charAt(0);
}
}
案例五:输出杨辉三家的第m行
规律:该元素的值为上方元素的值加上左上方元素的值,第一列和每行的最后一个元素为1。
public class Main {
// 杨辉三角的第m行
public static void main(String[] args) {
int m=3;
// 从第0行开始,第i行有i+1个元素
for (int i = 0; i <= m; i++) {
System.out.print(f(m,i)+" ");
}
}
private static int f(int m, int n) {
if(n==0) return 1;//从第0列开始
if(n==m) return 1;
return f(m-1,n)+f(m-1,n-1);
}
}
案例六:m个a,n个b,有多少种排列方式
解题思路:从第一个位置开始,该位置要么为a,要么为b,剩下的部分是与原问题类似,且规模更小。
public class Main {
// m个a,n个b,有多少种排列方式
public static void main(String[] args) {
System.out.println(f(3,2));
}
private static int f(int m, int n) {
if(m==0||n==0) return 1;
return f(m-1,n)+f(m,n-1);
}
}
案例七:整数划分问题
解题思路:依次把n到1作为第一个加数,剩下的问题是与原问题相同,且规模更小的问题
public class Main {
// 整数划分问题
public static void main(String[] args) {
int []a=new int[1000];
f(6,a,0);
}
// 对n进行划分
// a[]用于暂存加数,k当前存储位置
private static void f(int n,int []a,int k) {
if(n==0){
for (int i = 0; i < k; i++) {
System.out.print(a[i]+" ");
}
System.out.println();
return;
}
for(int i=n;i>0;i--){
if(k>0&&a[k-1]<i) continue;
a[k]=i;
f(n-i,a,k+1);
}
}
}
案例八:账目错误问题
问题描述:某财务部门结账时发现总金额不对头。很可能是从明细上漏掉了某1笔或几笔。如果已知明细账目清单,能通过编程找到漏掉的是哪1笔或几笔吗?
解题思路:该笔账目要么记录了,要么没被记录,剩下的问题与原问题相似,且问题的规模更小。
public class Main {
// 账目错误
/*
* 问题描述:某财务部门结账时发现总金额不对头。
很可能是从明细上漏掉了某1笔或几笔。如果已知
明细账目清单,能通过编程找到漏掉的是哪1笔或几笔吗?
*/
public static int sum=6;
public static int []a={3,2,4,3,1};
public static void main(String[] args) {
boolean []b=new boolean [a.length];
f(0,0,b);
}
// 对n进行划分
// a[]用于暂存加数,k当前存储位置
// k:当前处理的位置上
// cur_sum:前边的元素累加和
// b:记录当前项是否被选取
private static void f(int k,int cur_sum,boolean []b) {
// 出口
if(cur_sum>sum) return;
if(cur_sum==sum){
for (int i = 0; i < b.length; i++) {
if(!b[i]){
System.out.print(a[i]+" ");
}
}
System.out.println();
return;
}
if(k>=a.length) return;
// 漏了
b[k]=false;
f(k+1,cur_sum,b);
// 没漏
b[k]=true;
cur_sum+=a[k];
f(k+1,cur_sum,b);
// 回溯,默认他是没有被选取的
b[k]=false;
}
}