暴力递归
暴力递归就是尝试
- 1)把问题转化为规模缩小了的同类问题的子问题
- 2)有明确的不需要继续进行递归的条件(basecase)
- 3)有当得到了子问题的结果之后的决策过程
- 4)不记录每一个子问题的解
熟悉什么叫尝试?
- 打印n层汉诺塔从最左边移动到最右边的全部过程
- 打印一个字符串的全部子序列
- 打印一个字符串的全部子序列,要求不要出现重复字面值的子序列
- 打印一个字符串的全部排列
- 打印一个字符串的全部排列,要求不要出现重复的排列
汉诺塔
打印n层汉诺塔从最左边移动到最右边的全部过程
package com.harrison.class12;
public class Code01_Hanoi {
public static void func(int N,String from,String to,String other) {
if(N==1) {
System.out.println("Move 1 from "+from+" to "+to);
}else {
func(N-1,from,other,to);
System.out.println("Move "+N+" from "+from+" to "+to);
func(N-1,other,to,from);
}
}
public static void hanoi(int n) {
if(n>0) {
func(n,"left","right","mid");
}
}
public static void main(String[] args) {
int n=3;
hanoi(n);
}
}
打印一个字符串的全部子序列 && 打印一个字符串的全部子序列,要求不要出现重复字面值的子序列
字符串的子串和子序列有什么区别?
- 字符串的子串:必须是连续的一段
- 字符串的子序列:可以不连续,但是相对次序不能乱(每个字符要跟不要所有的路都走一遍)——深度优先遍历
package com.harrison.class12;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class Code03_PrintAllSubsquences {
/**
*
* @param str 固定字符串,不变
* @param index 当前来到的位置,要或者不要
* @param ans 如果index来到了str的终点位置,把沿途路径所形成的答案,放入ans中
* @param path 沿途路径:之前做出的选择就是path
*/
public static void process1(char[] str,int index,List<String> ans,String path) {
if(index==str.length) {
ans.add(path);
return;
}
// 没有要index位置的字符
String no=path;
process1(str,index+1,ans,no);
// 要了index位置的字符
String yes=path+String.valueOf(str[index]);
process1(str,index+1,ans,yes);
}
public static List<String> subs(String s){
char[] str=s.toCharArray();
List<String> ans=new ArrayList<>();
String path="";
process1(str,0,ans,path);
return ans;
}
// 集合自动去重
public static void process2(char[] str,int index,HashSet<String> set,String path) {
if(index==str.length) {
set.add(path);
return;
}
process2(str,index+1,set,path);
process2(str,index+1,set,path+String.valueOf(str[index]));
}
public static List<String> subsNoRepeat(String s){
char[] str=s.toCharArray();
HashSet<String> set=new HashSet<>();
String path="";
process2(str,0,set,path);
List<String> ans=new ArrayList<>();
for(String cur:set) {
ans.add(cur);
}
return ans;
}
public static void main(String[] args) {
String test="accc";
List<String> ans1=subs(test);
List<String> ans2=subsNoRepeat(test);
for(String str:ans1) {
System.out.println(str);
}
System.out.println("===================");
for(String str:ans2) {
System.out.println(str);
}
}
}
打印一个字符串的全部排列 && 打印一个字符串的全部排列,要求不要出现重复的排列
第二问可以用暴力递归加过滤(也就是去重)的做法,更好的办法是分支限界(是一种提前杀死支路的暴力递归)
package com.harrison.class12;
import java.util.ArrayList;
import java.util.List;
public class Code04_PrintAllPermutation {
/**
*
* @param str str[0~i-1] 已经做好决定的
* @param i i位置及其i以后的位置都有机会来到i位置
* @param ans i终止位置 str当前的样子就是一种结果,放入到ans中
*/
public static void process1(char[] str, int i, ArrayList<String> ans) {
if (i == str.length) {
ans.add(String.valueOf(str));
}
// 如果i没有终止,i及其i后面的位置都有机会来到i位置
for (int j = i; j < str.length; j++) {
// j 尝试i及其i后面的位置
swap(str, i, j);
process1(str, i + 1, ans);
// 一定要恢复现场,否则会有脏数据
swap(str, i, j);
}
}
public static void swap(char[] chr, int i, int j) {
char c = chr[i];
chr[i] = chr[j];
chr[j] = c;
}
public static ArrayList<String> permutation1(String str) {
ArrayList<String> res = new ArrayList<>();
if (str == null || str.length() == 0) {
return res;
}
char[] chs = str.toCharArray();
process1(chs, 0, res);
return res;
}
public static void process2(char[] str, int i, ArrayList<String> ans) {
if (i == str.length) {
ans.add(String.valueOf(str));
}
// visit[0...25] --> [a,b...z]
boolean[] visit = new boolean[26];
for (int j = i; j < str.length; j++) {
if (!visit[str[j] - 'a']) {
visit[str[j]-'a']=true;
swap(str, i, j);
process2(str, i + 1, ans);
swap(str, i, j);
}
}
}
public static ArrayList<String> permutation2(String str) {
ArrayList<String> res = new ArrayList<>();
if (str == null || str.length() == 0) {
return res;
}
char[] chs = str.toCharArray();
process2(chs, 0, res);
return res;
}
public static void main(String[] args) {
String test="acc";
List<String> ans1=permutation1(test);
List<String> ans2=permutation2(test);
for(String str:ans1) {
System.out.println(str);
}
System.out.println("===================");
for(String str:ans2) {
System.out.println(str);
}
}
}
仰望好的尝试?
给你一个栈,请你逆序这个栈,不能申请额外的数据结构,只能使用递归函数。如何实现?
package com.harrison.class12;
import java.util.Stack;
public class Code02_ReverseStackUsingRecrusive {
//栈底元素移除、上面的元素盖下来,返回栈底元素
public static int f(Stack<Integer> stack) {
int result=stack.pop();
if(stack.isEmpty()) {
return result;
}else {
// last f函数所返回的栈底元素
int last=f(stack);
stack.push(result);
return last;
}
}
public static void reverse(Stack<Integer> stack) {
if(stack.isEmpty()) {
return;
}else {
int i=f(stack);
reverse(stack);
stack.push(i);
}
}
public static void main(String[] args) {
Stack<Integer> test=new Stack<Integer>();
test.push(1);
test.push(2);
test.push(3);
test.push(4);
test.push(5);
reverse(test);
while(!test.isEmpty()) {
System.out.println(test.pop());
}
}
}
四种常见尝试模型
从左往右的尝试模型
模型1
规定1和A对应、2和B对应、3和C对应…那么一个数字字符串比如"111”就可以转化为:'AAA"、 “KA"和"AK”。给定一个只有数字字符组成的字符串str,返回有多少种转化结果
package com.harrison.class12;
public class Code05_ConvertToLetterString {
// str只含有数字字符0~9
// 返回多少种转化方案
public static int number(String str) {
if(str==null || str.length()==0) {
return 0;
}
return process(str.toCharArray(),0);
}
// str[0...i-1]转化无需过问
// str[i...]去转化,返回有多少种转化方法
public static int process(char[] str,int i) {
if(i==str.length) {
return 1;
}
if(str[i]=='0') {
return 0;
}
// str[i]!='0' && i没有来到终止位置
// 可能性1,i单转
int ways=process(str,i+1);
if(i+1<str.length && ((str[i]-'0')*10+str[i+1]-'0')<27) {
ways+=process(str,i+1);
}
return ways;
}
}
模型2
给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表i号物品的重量和价值。给定一个正数bag,表示一个载重bag的袋子,你装的物品不能超过这个重量。返回你能装下最多的价值是多少?
package com.harrison.class12;
public class Code06_Knapsack {
// 不变 w[] v[] bag
// alreadyW [0...index-1]上做了货物的选择,使得你已经达到的重量是多少
// index及其往后位置的货物自由做选择 返回其最大价值
// 如果返回-1 认为没有方案 如果不返回-1 认为返回的价值是真实价值
public static int process1(int[] w, int[] v, int index, int alreadyW, int bag) {
if (alreadyW > bag) {
return -1;
}
// 重量没超但是又没货了
if (index == w.length) {
return 0;
}
// p1:没有要当前货物的价值,接下来的货物产生的最大价值
int p1 = process1(w, v, index + 1, alreadyW, bag);
// p2next:要了当前货物的价值,后面的货物产生的最大价值
int p2next = process1(w, v, index + 1, alreadyW + w[index], bag);
int p2 = -1;
if (p2next != -1) {
p2 = v[index] + p2next;
}
return Math.max(p1, p2);
}
public static int getMaxValue1(int[] w, int[] v, int bag) {
return process1(w, v, 0, 0, bag);
}
// index... index及其往后位置的货物自由做选择 返回其最大价值
// 只剩下rest空间了
public static int process2(int[] w, int[] v, int index, int rest) {
if (rest < 0) {
return -1;
}
// rest>=0 且 没有货物
if (index == w.length) {
return 0;
}
// 有货物 && 有空间
int p1 = process2(w, v, index + 1, rest);
int p2 = -1;
int p2next = process2(w, v, index + 1, rest - w[index]);
if (p2next != -1) {
p2 = v[index] + p2next;
}
return Math.max(p1, p2);
}
public static int getMaxValue2(int[] w, int[] v, int bag) {
return process2(w, v, 0, bag);
}
public static void main(String[] args) {
int[] weights = {
3, 2, 4, 7, 3, 1, 7 };
int[] values = {
5, 6, 3, 19, 12, 4, 2 };
int bag = 15;
System.out.println(getMaxValue1(weights, values, bag));
System.out.println(getMaxValue2(weights, values, bag));
}
}
范围上尝试的模型
给定一个整型数组arr,代表数值不同的纸牌排成一条线,玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。
博弈论:双方玩家都不会在对方单独改变策略的情况下让对方获得最大收益
其它类似的零和博弈问题:鳄鱼吃人、海盗分金币、欧拉信封等等
package com.harrison.class12;
public class Code07_CardsInLine {
public static int f1(int[] arr, int l, int r) {
if (l == r) {
return arr[l];
}
return Math.max(arr[l] + s1(arr, l + 1, r), arr[r] + s1(arr, l, r - 1));
}
public static int s1(int[] arr, int l, int r) {
if (l == r) {
// 只剩一张牌,又是后拿
return 0;
}
return Math.min(f1(arr, l + 1, r), f1(arr, l, r - 1));
}
public static int win1(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
return Math.max(f1(arr, 0, arr.length - 1), s1(arr, 0, arr.length - 1));
}
public static void main(String[] args) {
int[] arr = {
5, 7, 4, 5, 8, 1, 6, 0, 3, 4, 6, 1, 7 };
System.out.println(f1(arr,0,arr.length-1));
System.out.println(s1(arr,0,arr.length-1));
}
}
N皇后
N皇后问题是指在N*N的棋盘上要摆N个皇后,要求任何两个皇后不同行、不同列,也不在同一条斜线上。给定一个整数n,返回n皇后的摆法有多少种。n=1,返回1。n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0。n=8,返回92
package com.harrison.class12;
import java.util.Scanner;
public class Code08_NQueens {
// 潜台词:record[0...i-1]的皇后,任何两个皇后一定都不共行、不共列,不共斜线
// 目前来到了第i行
// record[0...i-1]表示之前的行,放了的皇后位置
// n代表整体一共有多少行 0~n-1行
// 返回值是 摆完所有的皇后,合理的摆法有多少种
public static int process1(int i, int[] record, int n) {
if (i == n) {
// 终止行
return 1;
}
int res = 0;
for (int j = 0; j < n; j++) {
// 当前行在i行,尝试i行所有的列 -> j
// 当前i行的皇后,放在j列,会不会和之前[0...i-1]的皇后,不共行共列或者共斜线
// 如果是,认为有效
// 如果不是,认为无效
if (isValid(record, i, j)) {
record[i] = j;
res += process1(i + 1, record, n);
}
}
return res;
}
public static boolean isValid(int[] record, int i, int j) {
for (int k = 0; k < i; k++) {
// 之前的某个K行的皇后
// k record[k] i,j
if (j == record[k] || (Math.abs(record[k] - j) == Math.abs(i - k))) {
return false;
}
}
return true;
}
public static int num1(int n) {
if (n < 1) {
return 0;
}
int[] record = new int[n]; // record[i] -> i行的皇后 放在了第几列
return process1(0, record, n);
}
public static int process2(int limit, int colLim, int leftDiaLim, int rightDiaLim) {
if (colLim == limit) {
// base case
return 1;
}
// 所有候选皇后的位置,都在pos上
// (colLim | leftDiaLim | rightDiaLim) 总限制
// ~(colLim | leftDiaLim | rightDiaLim) 左侧的一坨0干扰,右侧每个1,可尝试
int pos = limit & (~(colLim | leftDiaLim | rightDiaLim));
int mostRightOne = 0;
int res = 0;
while (pos != 0) {
mostRightOne = pos & ((~pos) + 1);
pos = pos - mostRightOne;
res += process2(limit, colLim | mostRightOne, (leftDiaLim | mostRightOne) << 1,
(rightDiaLim | mostRightOne) >>> 1);
}
return res;
}
// 不要超过32个皇后
public static int num2(int n) {
if (n < 1 || n > 32) {
return 0;
}
// 如果是13皇后问题,limit最右13个1,其它都是0
int limit = n == 32 ? -1 : (1 << n) - 1;
return process2(limit,0,0,0);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入皇后个数:");
int n = sc.nextInt();
long start = System.currentTimeMillis();
System.out.println(num1(n));
long end = System.currentTimeMillis();
System.out.println("cost time:" + (end - start) + "ms");
start = System.currentTimeMillis();
System.out.println(num2(n));
end = System.currentTimeMillis();
System.out.println("cost time:" + (end - start) + "ms");
}
}