12.暴力递归

暴力递归

暴力递归就是尝试

  • 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");
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_44337241/article/details/121297359
今日推荐