7 深入递归
7.1 小白上楼梯问题
题目:有一个小孩上楼梯,楼梯有n阶台阶,小孩可以一次上1,2,3阶。请实现一个方法,计算小孩有多少种上楼梯的方式。
楼梯 走法
1 1
2 2
3 4
4 7
5 13
有规律,假设走(1,2,3)梯,还剩几阶楼梯,加上对应的走法即可。前面都已经求出来了。
f(n) = f(n-1) +f(n-2) +f(n-3)
// 方式1:递归实现,虽然简洁,但是在会出现超时!
public int waysToStep(int n) {
if(n == 1)
return 1;
if(n == 2)
return 2;
if(n == 3)
return 4;
return waysToStep(n - 1) % 1000000007 + waysToStep(n - 2) % 1000000007 + waysToStep(n - 3) % 1000000007;
}
// 方式2:
public int waysToStep(int n) {
if(n == 1)
return 1;
if(n == 2)
return 2;
if(n == 3)
return 4;
int[] dp = new int[n+1];
dp[1] = 1;
dp[2] = 2;
dp[3] = 4;
for(int i = 4;i < dp.length;i++){
//取模,对两个较大的数之和取模再对整体取模,防止越界(这里也是有讲究的)
//假如对三个dp[i-n]都 % 1000000007,那么也是会出现越界情况(导致溢出变为负数的问题)
//因为如果本来三个dp[i-n]都接近 1000000007 那么取模后仍然不变,但三个相加则溢出
//但对两个较大的dp[i-n]:dp[i-2],dp[i-3]之和mod 1000000007,
//那么这两个较大的数相加大于 1000000007但又不溢出
//取模后变成一个很小的数,与dp[i-1]相加也不溢出
//所以取模操作也需要仔细分析
dp[i] = (dp[i-1] + (dp[i-2] + dp[i-3]) % 1000000007) % 1000000007;
}
return dp[n];
}
7.2 机器人走方格问题
有一个X * Y的网格,一个机器人只能走格点且只能向右或向下走,要从左上角走到右下角。请设计一个算法,计算机器人有多少种走法。
给定两个正整数int x,int y,请返回机器人的走法数目。保证x+y小于等于12。
举例子:(2,2)返回:2
即2 * 2的网格中,机器人从左上角走到右上角的走法有2种。
先一点一点推理:
(1)、x = y =1 1
(2)、x=1,y=2 1
(3)、x=2,y=1 1
(4)、x=y=2 2 (往右走变成 x=2,y=1; 往下走变成x=1,y=2。2=1+1)
(5)、x=2,y=3 3
(6)、x=3,y=2 3
(7)、x=y=3 6
递推公式:
f(x,y) = f(x-1,y) + f(x,y-1);
// 方案1:递归实现
public int countWays(int x, int y) {
if(x == 1 || y == 1)
return 1;
return countWays(x-1,y) + countWays(x,y-1);
}
// 方案2: 递推
public int countWays(int x, int y) {
int[][] a=new int[x+1][y+1];
// 二维数组初始化所有值为1
for (int i = 1; i <=x; i++) {
a[i][1]=1;
}
for (int j = 1; j <=y; j++) {
a[1][j]=1;
}
// 递推计算
for (int i = 2; i <=x ; i++) {
for (int j = 2; j <=y ; j++) {
a[i][j]=a[i-1][j]+a[i][j-1];
}
}
// 返回结果
return a[x][y];
}
7.3 硬币表示问题
题目:硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)
示例1:
输入: n = 5
输出:2解释: 有两种方式可以凑成总金额:
5=5
5=1+1+1+1+1
一点一点推理:1 5 15 25
面值 1 2 3 4 5 6 7 8 9 10 15
种类 1 1 1 1 2 2 2 2 2 4 6
public class Test31 {
public static void main(String[] args) {
System.out.println(waysToChange(50));
System.out.println(countWays2(50));
}
static int waysToChange(int n) {
if (n <= 0)
return 0;
int[] coins = { 1, 5, 10, 25 };// 钱币面值数组
return countWays(n, coins, 3);// 从下标3对应的最大面值开始递归
}
/**
* 统计n分有几种表示法(递归方式)
*
* @param money
* @param coins
* @param index
* @return
*/
static int countWays(int money, int[] coins, int index) {
if (index == 0)
return 1;
int result = 0;
for (int i = 0; coins[index] * i <= money; i++) {
int shengyu = money - coins[index] * i;// 剩余金额
result = result + countWays(shengyu, coins, index - 1);// 表示法数量
}
return result;
}
/**
* 统计n分有几种表示法(动态规划方式)
*
* @param n 总金额
* @return
*/
static int countWays2(int n) {
int mod = 1000000007;
int[] dp = new int[n + 1];// dp[n] 就表示:n分表示法总数
int[] coins = new int[]{1,5,10,25};// 钱币面值数组
dp[0] = 1;// 不使用任何硬币,表示0元有一种方法
for (int coin : coins) {// 每一次循环,使用新面值coin
for (int i = coin; i <= n; i++) {// 增加新面值后,方法总数受影响的是从coin开始的
dp[i] = (dp[i] + dp[i - coin]) % mod;
// 方法总数 = 使用新的面值方法总数((j-面值)的方法总数) + 不使用新面值的方法总数(为原来值)
}
}
return dp[n];
}
}
7.4 逐步生成结果” 类问题之非数值型:括号
打印括号
n=1:()
n=2;()() ,(())
n=3:()()(), (()()), ()(()), (())(), ((()))
import java.util.HashSet;
public class 逐步生成结果_非数值型 {
public static void main(String[] args) {
HashSet<String> SET = new HashSet<String>();
int n=3;
SET=f(n);
System.out.println(SET);
}
///方法1:
// private static HashSet<String> f(int n) {
// // TODO Auto-generated method stub
// HashSet<String> set = new HashSet<String>();
// if (n==1) {
// set.add("()");
// return set;
// }
// HashSet<String> se1 = f(n-1);
// for (String s:se1) {
// set.add("()"+s);
// set.add(s+"()");
// set.add("("+s+")");
// }
// return set;
// }
// 迭代形式
private static HashSet<String> f(int n) {
// TODO Auto-generated method stub
HashSet<String> set = new HashSet<String>();
set.add("()");
if (n == 1) {
return set;
}else {
for (int i = 2; i <= n; i++) {
HashSet<String> set2 = new HashSet<String>();
for (String s:set) {
set2.add("()"+s);
set2.add(s+"()");
set2.add("("+s+")");
}
set = set2;
}
return set;
}
}
}
7.5 子集生成
题目:写一个方法,返回集合的所有子集
{A,B,C}
子集:{{A},{B},{C},{AB},{AC},{BC},{ABC}}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-58BfbKpU-1643460194501)(F:\soft\Typora\蓝桥杯笔记\蓝桥杯笔记7-8.assets\5783cb80fc0db5ec290056b23781e921.png)]
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
public class 子集生成 {
public static void main(String[] args) {
String[] array = { "A", "B", "C" };
HashSet<HashSet<String>> Set = new HashSet<HashSet<String>>();
HashSet<HashSet<String>> Set2 = new HashSet<HashSet<String>>();
ArrayList<ArrayList<String>> Set3 = new ArrayList<ArrayList<String>>();
Set = f(array, array.length);
Set2 = f2(array, array.length);
Set3 = f3(array, array.length);
System.out.println("递归法求子集");
System.out.println(Set);
System.out.println("迭代法求子集");
System.out.println(Set2);
System.out.println("二进制法求子集");
System.out.println(Set3);
}
递归
private static HashSet<HashSet<String>> f(String[] a, int length) {
// TODO Auto-generated method stub
HashSet<HashSet<String>> set = new HashSet<HashSet<String>>();
if (length == 0) {
set.add(new HashSet<String>());
return set;
}
HashSet<HashSet<String>> set2 = f(a, length - 1);
for (HashSet<String> list : set2) {
set.add(list);
@SuppressWarnings("unchecked")
HashSet<String> cloneHashSet = (HashSet<String>) list.clone();
cloneHashSet.add(a[length - 1]);
set.add(cloneHashSet);
}
return set;
}
迭代
private static HashSet<HashSet<String>> f2(String[] array, int length) {
// TODO Auto-generated method stub
HashSet<HashSet<String>> set = new HashSet<HashSet<String>>();
set.add(new HashSet<String>());
for (int i = 0; i < array.length; i++) {
HashSet<HashSet<String>> set2 = new HashSet<HashSet<String>>();
set2.addAll(set);
for (HashSet<String> liStrings : set) {
@SuppressWarnings("unchecked")
HashSet<String> cloneHashSet = (HashSet<String>) liStrings.clone();
cloneHashSet.add(array[i]);
set2.add(cloneHashSet);
}
set = set2;
}
return set;
}
///二进制法求解
private static ArrayList<ArrayList<String>> f3(String[] array, int length) {
// TODO Auto-generated method stub
ArrayList<ArrayList<String>> list = new ArrayList<ArrayList<String>>();
for (int i = 0; i <Math.pow(2, length); i++) {
ArrayList<String> intStrings = new ArrayList<String>();
for (int j = length-1; j >=0; j--) {
if (((i >> j)&1)== 1) {
intStrings.add(array[j]);
}
}
list.add(intStrings);
}
return list;
}
}
7.6 全排列(1)
{A,B,C} 排列
ABC,ACB,BAC,BCA,CAB,CBA 六种 3* 2 *1 第一次三种选择,第二次两种选择,最后一次一种选择。
1 A
2 BA AB 放在A的前面和A的后面
import java.util.ArrayList;
public class 全排列 {
public static void main(String[] args) {
String array = "ABC";
ArrayList<String> list = new ArrayList<String>();
list = f(array);
System.out.println(list);
}
private static ArrayList<String> f(String array) {
// TODO Auto-generated method stub
int n = array.length();
ArrayList<String> list = new ArrayList<String>();
list.add(array.charAt(0) + "");
for (int i = 1; i < n; i++) {
ArrayList<String> newList = new ArrayList<String>();
char c = array.charAt(i);
for (String str : list) {
String newString = c + str;
newList.add(newString);
newString = str + c;
newList.add(newString);
for (int j = 1; j < str.length(); j++) {
newString = str.substring(0,j)+c+str.substring(j);
newList.add(newString);
}
}
list = newList;
}
return list;
}
}
//结果:
递归法求子集
[[], [A], [B], [C], [A, B], [A, C], [B, C], [A, B, C]]
迭代法求子集
[[], [A], [B], [A, B], [C], [A, C], [B, C], [A, B, C]]
二进制法求子集
[[], [A], [B], [B, A], [C], [C, A], [C, B], [C, B, A]]
7.7 全排列(2)
回溯:不断交换位置
【A,B,C】
[ABC] [BAC] []
//回溯法
static ArrayList<String> list = new ArrayList<String>();
private static ArrayList<String> f2(String array) {
// TODO Auto-generated method stub
char[] arr = array.toCharArray();
Arrays.sort(arr);
getPermutation(arr, 0);
return list;
}
private static void getPermutation(char[] arr, int n) {
// TODO Auto-generated method stub
if (n == arr.length) {
list.add(new String(arr));
}
for (int j = n; j < arr.length; j++) {
swap(arr, j, n);
getPermutation(arr, n+1);
swap(arr, n, j);
}
}
private static void swap(char[] arr, int j, int i) {
// TODO Auto-generated method stub
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
7.8 全排列(3)
每次都从头开始扫描:通过for循环
【A,B,C】
[A] [B] [C]
[AB] [AC] [BA] [BC] [CA] [CB]
[ABC] [ACB] [BAC] [BCA] [CAB] [CBA]
public static void f3(String s,char[] arr) {
if (s.length() == 3) {
System.out.println(s);
return;
}
for (int i = 0; i < arr.length; i++) {
char ch = arr[i];
if (!s.contains(ch+"")) {
f3(s+ch,arr);
}
}
}
7.9 DFS例题1:数独游戏
9*9的一个盘面,满足每一行,每一列,每一个同色九宫格数字均含1-9不重复,数独的答案都是唯一的。
//伪代码
dfs(table,x,y){
if(x == 9){
print(table);
System.exit(0);
}
if(table[x][y] == '0'){
//选1-9之间合法的数字填到x,y这个位置
for(int i=1..9){
//检查x行和y列不能有i,九宫格不能有i
boolean res= check(table,x,y,i);
if(res){
table[x][y] = i//转移到下一个状态
dfs(table,x+(y+1)/9,(y+1)%9);
table[x][y] = '0';//回溯
}
}
}else{
//找下一个处理的位置
dfs(table,x+(y+1)/9,(y+1)%9);
}
}
//java代码
package 第七章_深度递归;
import java.util.Scanner;
//005300000
//800000020
//070010500
//400005300
//010070006
//003200080
//060500009
//004000030
//000009700
public class 数独 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
char[][] table = new char[9][];
for (int i = 0; i < 9; i++) {
table[i] = scanner.nextLine().toCharArray();
}
dfs(table,0,0);
}
private static void dfs(char[][] table, int x,int y){
if (x == 9) {
print(table);
System.exit(0);
}
if (table[x][y] == '0') {
for (int i = 1; i < 10; i++) {
boolean res = check(table,x,y,i);
if (res) {
table[x][y] = (char)('0'+i);
dfs(table,x+(y+1)/9,(y+1)%9);
}
}
table[x][y] = '0';//回溯
}else {
dfs(table,x+(y+1)/9,(y+1)%9);
}
}
private static void print(char[][] table) {
// TODO Auto-generated method stub
for (int i = 0; i < table.length; i++) {
for (int j = 0; j < table[i].length; j++) {
System.out.print(table[i][j]);
}
//System.out.println(Arrays.toString(table[i]));
System.out.println();
}
}
private static boolean check(char[][] table, int x, int y, int i) {
// TODO Auto-generated method stub
//检查一行和一列
for (int j = 0; j < 9; j++) {
if (table[x][j] == (char)('0'+i)) {
return false;
}
if (table[j][y] == (char)('0'+i)) {
return false;
}
}
//检查九宫格
for (int j = (x/3)*3; j <(x/3+1)*3 ; j++) {
for (int j2 = (y/3)*3; j2 < (y/3+1)*3; j2++) {
if (table[j][j2] == (char)('0'+i)) {
return false;
}
}
}
return true;
}
}
7.10 DFS例题2:部分和
在给定的整数序列:a1,a2,a3…an。判断是否可以从中选出若干数,使他们的和恰好为K
思路1:把所有子集列出来
思路2:DFS
package 第七章_深度递归;
import java.util.ArrayList;
import java.util.Scanner;
public class 部分和 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] a = new int[n];
for (int i = 0; i < a.length; i++) {
a[i] = scanner.nextInt();
}
int k = scanner.nextInt();
dfs(a,k,0,new ArrayList<Integer>());
}
private static void dfs(int[] a, int k,int cur,ArrayList<Integer> list) {
// TODO Auto-generated method stub
if (k == 0) {
System.out.println("yes:");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+(i == list.size()-1?"":"+"));
}
System.exit(0);
}
if (k < 0 || cur == a.length) {
return;
}
dfs(a, k, cur+1,list);//不要这个cur元素
list.add(a[cur]);
int index = list.size()-1;
dfs(a, k-a[cur], cur+1,list);//要这个元素
list.remove(index);//回溯
}
}
7.11 DFS例题3:水洼数目
有一个N*M大小的园子,八连通的积水被认为是连在一起的。请求出园子里有多少水洼。
000
010 为一个水洼
000
import java.util.Scanner;
public class 水洼数 {
static int M, N;
// next()在输入有效字符之后,将其后输入的空格键、Tab键或Enter键等视为分隔符或结束符。
// nextLine()方法的结束符只是Enter键
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("输入行数:");
N = scanner.nextInt();
scanner.nextLine();
System.out.println("输入列数:");
M = scanner.nextInt();
scanner.nextLine();//用scanner,nextLine吸收回车
char[][] a = new char[N][];
System.out.println("输入字符数组");
for (int i = 0; i < a.length; i++) {
a[i] = scanner.nextLine().toCharArray();
}
int count = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (a[i][j] == 'W') {
dfs(a, i, j);
count++;
}
}
}
System.out.println(count);
}
private static void dfs(char[][] a, int i, int j) {
// TODO Auto-generated method stub
a[i][j] = '.';
for (int k = -1; k < 2; k++) {
for (int l = -1; l < 2; l++) {
if (k == 0 && l == 0) {
continue;
}
if (i + k >= 0 && i + k <= N - 1 && j + l >= 0 && j + l <= M - 1) {
if (a[i + k][j + l] == 'W') {
dfs(a, i + k, j + l);
}
}
}
}
}
}
7.12 DFS例题4:n皇后问题
回溯:
递归调用代表开启一个分支,如果希望这个分支返回后某些数据恢复到分支开启前的状态以便重新开始,就要使用回溯技巧。
全排列的交换法,数独,部分和,用到了回溯。
剪枝:
深搜时,如已明确从当前状态无论如何转移都不会存在(更优)解,就应该中断往下的继续搜索,这种方法称为剪枝
数独里面有剪枝。
部分和里面有剪枝
题目:设计一种算法,在n*n的棋盘上放置n个棋子,使得每行和每列和每条对角线上都有一个棋子,求其摆放的方法数。
//伪代码
dfs(0,0)
int n;
int cnt;
int[] rec;
dfs(rec,row){
if(row == n+1){
cnt++
return;
}
for(col from 1..n){
if(check(rec,row,col)){
rec[row] = col;//假设row这一行把皇后放在col这一列
dfs[rew,row+1];
rec[row] = 0;//回溯
}
}
}
check(rec,x,y){
for(i=0;i<rec.length;i++){
if(rec[i] == y){
return false;
}
if(i+rec[i] == x+y){
return false;
}
if(i-rec[i] == x-y){
return false;
}
}
return true;
}
//java代码
public class n皇后问题 {
static int n;
static int cnt;
static int[] rec;
public static void main(String[] args) {
n=4;
rec = new int[n];
dfs(0);
System.out.println(cnt);
}
private static void dfs(int row) {
// TODO Auto-generated method stub
if (row == n) {
cnt++;
return;
}
for (int col = 0; col < rec.length; col++) {
boolean ok = true;
//检验这个皇后和之前的皇后是否冲突
for (int i = 0; i < row; i++) {
if (rec[i] == col||i+rec[i] ==row+col||rec[i]-i==col-row) {
ok = false;
break;
}
}
///剪枝
if (ok) {
rec[row] = col;
dfs(row+1);
//rec[row]=0//不回溯也可以
}
}
}
}
7.13 DFS例题5:素数环
输入正整数n,对1-n进行排列,使得相邻的两个数之和均为素数。
输出时从整数1开始,逆时针排列。
同一个环应正好输出一次。
输入:
6
输出:
1 4 3 2 5 6
1 6 5 2 3 4
7.14 DFS例题6:困难的串
如果一个字符串包含两个相邻的重复子串,则称它为容易的串,其他串称为困难的串,
如:BB, ABCDACABCAB ABCDABCD 都是容易的
D DC ABDAB CBABCAB 都是困难的