目录
暴力递归介绍
暴力递归的应用
汉诺塔问题
打印字符串子序列问题
打印一个字符串的全部排列
拿牌问题
栈的逆序
数字转化问题
装货物问题
N皇后问题
暴力递归就是尝试,按照下面的步骤:1,把问题转化为规模缩小了的同类问题的子问题 ;2,有明确的不需要继续进行递归的条件;3,有当得到了子问题的结果之后的决策过程; 4,不记录每一个子问题的解。
尝试的原则是找可变参数形式最简单,可变数量最少的方式。
打印n层汉诺塔从最左边移动到最右边的全部过程。
这个问题的解决首先是假设三个杆分别是from,to,other,我们的目标是将所有的圆盘从from移动到to,我们按照这样的步骤进行,先将1~n-1个圆盘从from移动到other,接着将第n个圆盘从from移动到to,最后再将1~n-1个圆盘从other移动到to,即可完成这个完整的过程。
public class Code_Hanoi{
public static void hanoi(int n){
if(n>0){
func(n,"左”,"右","中");
}
}
//将1~n-1个圆盘从from移动到other,将n从from移动到to,最后将1~n-1个圆盘从other移动到to
public static void func(int i,String start,String end,String other){
if( i==1 ){//终止条件
System.out.println("Move 1 from "+ start + "to" +end);
}
else{
func(i-1,start,other,end);
System.out.println("Move" + i + "from" +start+ "to" + end);
func(i-1,other,to,start);
}
}
public static void main(String[] args){
int n = 3;
hanoi(n);
}
}
打印一个字符串的全部子序列,包括空字符串。
先来到第一个字符的位置有要和不要两个选择,接着继续下去,直到最后一个字符就是一棵完全二叉树。
public class Code_PrintAllSubsquences{
public static void printAllSubsquence(String str){
char[] chs = str.toCharArray();
process(chs,0);
}
//当前来到i位置,要和不要,走两条路
//之前的选择所形成的结果是str
//这个方式更省空间
public static void process(char[] str,int i){
if(i==str.length){//如果到达终止位置,直接打印
System.out.println(String.valueOf(str));
return;
}
process(str ,i+1);//要当前字符的路
char tmp = str[i];//将i位置的路记录
str[i]=0;//改为0
process(str,i+1);//不要当前字符的路
str[i]=tmp;//继续改回去
}
public static void function(String str){
char[] chs = str.toCharArray();
process(chs,0,new ArrayList());
}
//当前来到i位置,要和不要,走两条路
//res记录着之前的选择所形成的列表
// public static void process(char[] str,int i,List res){
// if(i==str.length){//如果到达终止位置,直接打印
// printList(res);
// return;
// }
// List resKeep = copyList(res);//将之前形成的记录打印一份
// resKeep.add(str[i]);//将新的字符加进去
// process(str,i+1,resKeep);//表示要当前的路
// List resNoInclude = copyList(res);//将之前形成的记录打印一份
// process(str,i+1,resNoInclude);//表示不要当前的路
//}
要求不要出现重复的排列。
一个字符串的全部排列的输出,第一个位置有n种选择,第二个位置有n-1种选择,依次按照这种方式选择下去,解决这个问题,将一个字符串的前面一部分当作已经做好的选择,然后在i位置上将后面的每一个节点分别放在i位置上然后排列它的所有的后面的选择,选择完以后换回去,接着重复这个操作,直到全部字符完成全排列。
对于这个问题采用的是分支限界的方式,在排列选择的过程中,已经避免了重复的继续执行程序,相对于全部排列完,再删除重复的排列更快,而时间复杂度的量级并没有降低,在实际应用中,常数项可能有优化。
public class Code_PrintAllPermutations {
public static ArrayList Permutation(String str) {
ArrayList res = new ArrayList<>();
if (str == null || str.length() == 0) {
return res;
}
char[] chs = str.toCharArray();
process(chs, 0, res);
return res;
}
//str[i..]范围上,所有的字符,都可以在i位置上,后续都去尝试
//str[0..i-1]范围上,是之前做的选择
//把所有字符串形成的全排列,加入到res里去
public static void process(char[] chs, int i, ArrayList res) {
if (i == chs.length) {//如果全部排列好,直接添加到res中
res.add(String.valueOf(chs));
}
boolean[] visit = new boolean[26];
for (int j = i; j < chs.length; j++) {//每一个放到i位置进行选择
if (!visit[chs[j] - 'a']) {//如果这个字符没有出现过,执行循环,避免出现重复的排列
visit[chs[j] - 'a'] = true;
swap(chs, i, j);//将第j位置的字符换到i位置
process(chs, i + 1, res);//将i位置后面的字符分支全部情况选择好
swap(chs, i, j);//交换回去
}
}
}
public static void swap(char[] chs, int i, int j) {//交换函数
char tmp = chs[i];
chs[i] = chs[j];
chs[j] = tmp;
}
}
给定一个整型数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A 和玩家B都绝顶聪明。请返回最后获胜者的分数。
【举例】 arr=[1,2,100,4]。开始时,玩家A只能拿走1或4。如果开始时玩家A拿走1,则排列变为[2,100,4],接下来 玩家 B可以拿走2或4,然后继续轮到玩家A... 如果开始时玩家A拿走4,则排列变为[1,2,100],接下来玩家B可以拿走1或100,然后继 续轮到玩家A... 玩家A作为绝顶聪明的人不会先拿4,因为拿4之后,玩家B将拿走100。所以玩家A会先拿1, 让排列变为[2,100,4],接下来玩家B不管怎么选,100都会被玩家 A拿走。玩家A会获胜,分数为101。所以返回101。arr=[1,100,2]。 开始时,玩家A不管拿1还是2,玩家B作为绝顶聪明的人,都会把100拿走。玩家B会获胜,分数为100。所以返回100。
对于这个问题分为先手和后手去分析,根据题意写出递归条件和终止条件。
public static int win(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
return Math.max(f(arr, 0, arr.length - 1), s(arr, 0, arr.length - 1));//先手玩家和后手玩家谁大,谁获胜
}
public static int f(int[] arr, int i, int j) {//先手函数
if (i == j) {//如果只有一个数,先手直接拿了
return arr[i];
}
return Math.max(arr[i] + s(arr, i + 1, j), arr[j] + s(arr, i, j - 1));//否则,就是要么拿第一个然后在剩下的里面后手拿,或者拿最后一个,剩下的里面后手拿,选择其中最大的
}
public static int s(int[] arr, int i, int j) {//后手函数
if (i == j) {//如果只有一个,后手拿不到
return 0;
}
return Math.min(f(arr, i + 1, j), f(arr, i, j - 1));//否则,根据先手拿的最左边还是最右边,选择剩余的最小的,先手一定不对留给你大的
}
给你一个栈,请你逆序这个栈,不能申请额外的数据结构,只能使用递归函数。 如何实现?
public static void reverse(Stack stack) {//逆序栈
if (stack.isEmpty()) {//如果栈为空,直接返回
return;
}
int i = getAndRemoveLastElement(stack);//返回栈底元素,其它压下去
reverse(stack);
stack.push(i);//压入栈
}
public static int getAndRemoveLastElement(Stack stack) {//返回栈底元素,其它压下去
int result = stack.pop();//弹出栈顶元素
if (stack.isEmpty()) {//如果栈为空,直接返回
return result;
} else {
int last = getAndRemoveLastElement(stack);//继续调用自身
stack.push(result);//将result压入栈
return last;//返回栈底元素
}
}
规定1和A对应、2和B对应、3和C对应... 那么一个数字字符串比如"111",就可以转化为"AAA"、"KA"和"AK"。 给定一个只有数字字符组成的字符串str,返回有多少种转化结果。
//从左往右递归尝试
public static int number(String str) {
if (str == null || str.length() == 0) {
return 0;
}
return process(str.toCharArray(), 0);
}
//i之前的位置,如何转化已经做过决定了
//i之后有多少种转化的结果
public static int process(char[] chs, int i) {
if (i == chs.length) {//如果字符已经到了最后位置,返回之前位置做的决定
return 1;
}
if (chs[i] == '0') {//如果当前字符的0字符,之前做的决定存在问题,整体只有0种有效的决定
return 0;
}
if (chs[i] == '1') {//如果当前字符为1
int res = process(chs, i + 1);//i自己作为单独的部分,后续有多少种方法
if (i + 1 < chs.length) {//如果当前字符下面还有字符
res += process(chs, i + 2);//i和i+1作为单独的部分,后续还有多少种方法
}
return res;
}
if (chs[i] == '2') {//如果当前字符为2
int res = process(chs, i + 1);//i自己作为单独的部分,后续有多少种方法
if (i + 1 < chs.length && (chs[i + 1] >= '0' && chs[i + 1] <= '6')) {
res += process(chs, i + 2);//如果当前字符后面还有字符,并且两者结合不会超过26,i和i+1作为单独的部分,后续还有多少种方法
}
return res;
}
return process(chs, i + 1);//当前字符是3~9范围上
}
给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表i号物品的重量和价值。给定一个正数bag,表示一个载重bag的袋子,你装的物品不能超过这个重量。返回你能装下最多的价值是多少?
///这个问题依然是从左往右递归尝试
public static int maxValue(int[] weights, int[] values, int bag) {
return process1(weights, values, 0, 0, bag);
}
//i后面的货物自由选择,形成的最大价值返回
//alreadyweight之前做的决定,所达到的重量
public static int process1(int[] weights, int[] values, int i, int alreadyweight, int bag) {
if (alreadyweight > bag) {//如果超重了,不可以
return 0;
}
if (i == weights.length) {//如果没货了,最大价值为0
return 0;
}
return Math.max(
process1(weights, values, i + 1, alreadyweight, bag),//如果不要i号货物
values[i] + process1(weights, values, i + 1, alreadyweight + weights[i], bag));//要i号货物
}
N皇后问题是指在N*N的棋盘上要摆N个皇后,要求任何两个皇后不同行、不同列,也不在同一条斜线上。给定一个整数n,返回n皇后的摆法有多少种。 n=1,返回1。 n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0。n=8,返回92。
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 process1(int i, int[] record, int n) {
if (i == n) {//如果已经来到第n行,之前的是正确的,返回一种情况
return 1;
}
int res = 0;
for (int j = 0; j < n; j++) {//来到第i行,从第一列到第n-1列开始尝试
if (isValid(record, i, j)) {//如果和之前的i-1行的皇后既不共行共列,也不共斜线,那么认为当前位置有效,记录该位置
record[i] = j;
res += process1(i + 1, record, n);//继续往下尝试
}
}
return res;
}
//record[0..i-1]位置需要看,record[i..]的位置不需要看
//返回i行皇后,放在了j列,是否有效
public static boolean isValid(int[] record, int i, int j) {
for (int k = 0; k < i; k++) {//之前的某个k行的皇后
if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {//如果共列,或者共斜线,返回false
return false;
}
}
return true;
}
///加速算法,对上面的进行优化,思路一样
//请不要超过32皇后问题
public static int num2(int n) {
if (n < 1 || n > 32) {
return 0;
}
int upperLim = n == 32 ? -1 : (1 << n) - 1;//对于n皇后问腿,那么就是后面n个全是1,前面全是0
return process2(upperLim, 0, 0, 0);
}
//利用位运算加速
public static int process2(int upperLim, int colLim, int leftDiaLim,
int rightDiaLim) {//通过改变限制,进行皇后的选择,这几个变量存在,之前的选择造成的右限制,左斜线和右斜线限制
if (colLim == upperLim) {//全部放完,返回一种情况
return 1;
}
int pos = 0;
int mostRightOne = 0;
pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim));//哪些位置可以放皇后
int res = 0;
while (pos != 0) {//将pos中的1依次都提取出来
mostRightOne = pos & (~pos + 1);//提取出能选皇后位置的最右侧的1
pos = pos - mostRightOne;
res += process2(upperLim, colLim | mostRightOne,
(leftDiaLim | mostRightOne) << 1,//左斜线限制的更新
(rightDiaLim | mostRightOne) >>> 1);//右斜线限制的更新
}
return res;
}