算法---暴力递归
0.暴力递归的概念
暴力递归就是尝试
- 把问题转化为规模缩小了的同类问题的子问题
- 有明确的不需要继续进行递归的条件(base case)
- 有当得到了子问题的结果之后的决策过程
- 不记录每一个子问题的解(记录就是动态规划)
1.汉诺塔问题
程序员面试金典 面试题 08.06. 汉诺塔问题(easy)
LeetCode
先放题解
题解
class Solution {
public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
func(A.size(),A,B,C);
}
public static void func(int N,List<Integer> from, List<Integer> other, List<Integer> to){
if(N == 1){
to.add(from.remove(from.size()-1));
return;
}else{
func(N-1,from,to,other);
to.add(from.remove(from.size()-1));
func(N-1,other,from,to);
}
}
}讲解
汉诺塔移动盘子有如下的限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。
我们设有左中右三个柱子,有三个圆盘,当三个圆盘从小到大依次排列在右侧柱子时,游戏结束。
上面的暴力递归概念中说到,我们需要将大问题转化为小的问题,也就是将上面的1 2号圆盘设想为一个圆盘,
这样我们就可一将问题抽象为:
1、将融合圆盘(N-1)移动到中间
2、N号圆盘从左移动到右边
3、将融合圆盘(N-1)从中间移动到右边
融合的部分就是要进行递归的部分
有如下代码:
public static void hanoi1(int n) {
leftToRight(n);
}
public static void leftToRight(int n) {
if (n == 1) {
System.out.println("Move 1 from left to right");
return;
}
leftToMid(n - 1);
System.out.println("Move " + n + " from left to right");
midToRight(n - 1);
}
public static void leftToMid(int n) {
if (n == 1) {
System.out.println("Move 1 from left to mid");
return;
}
leftToRight(n - 1);
System.out.println("Move " + n + " from left to mid");
rightToMid(n - 1);
}
public static void rightToMid(int n) {
if (n == 1) {
System.out.println("Move 1 from right to mid");
return;
}
rightToLeft(n - 1);
System.out.println("Move " + n + " from right to mid");
leftToMid(n - 1);
}
public static void midToRight(int n) {
if (n == 1) {
System.out.println("Move 1 from mid to right");
return;
}
midToLeft(n - 1);
System.out.println("Move " + n + " from mid to right");
leftToRight(n - 1);
}
public static void midToLeft(int n) {
if (n == 1) {
System.out.println("Move 1 from mid to left");
return;
}
midToRight(n - 1);
System.out.println("Move " + n + " from mid to left");
rightToLeft(n - 1);
}
public static void rightToLeft(int n) {
if (n == 1) {
System.out.println("Move 1 from right to left");
return;
}
rightToMid(n - 1);
System.out.println("Move " + n + " from right to left");
midToLeft(n - 1);
}此时我们可以将这个模型抽象化:
无论是从左到右还是从右到左还是从左到中还是从中到左,总是有一个from,一个to,一个other
那么就有我们一开始的代码
public static void hanoi2(int n) {
if (n > 0) {
func(n, "left", "right", "mid");
}
}
// 1~i 圆盘 目标是from -> to, other是另外一个
public static void func(int N, String from, String to, String other) {
if (N == 1) { // base
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);
}
}2.尝试
1.打印一个字符串的全部子序列
给定一个字符串“abc”,输出全部的子序列 如下: c b bc a ac ab abc
对给定的字符串的每一个字符,都有两种可能,“要”或“不要”
那么思路逐渐清晰,如图所示:
public static List<String> subs(String s) {
char[] str = s.toCharArray();
String path = "";
List<String> ans = new ArrayList<>();
process1(str, 0, ans, path);
return ans;
}
public static void process1(char[] str, int index, List<String> ans, String path) {
if (index == str.length) {
ans.add(path);
return;
}
String no = path;
process1(str, index + 1, ans, no);
String yes = path + String.valueOf(str[index]);
process1(str, index + 1, ans, yes);
}2.打印一个字符串的全部子序列,要求不要出现重复字面值的子序列
这个和上面的一样,存储容器使用set就好了,自动去重,挺方便的
public static List<String> subsNoRepeat(String s) {
char[] str = s.toCharArray();
String path = "";
HashSet<String> set = new HashSet<>();
process2(str, 0, set, path);
List<String> ans = new ArrayList<>();
for (String cur : set) {
ans.add(cur);
}
return ans;
}
public static void process2(char[] str, int index, HashSet<String> set, String path) {
if (index == str.length) {
set.add(path);
return;
}
String no = path;
process2(str, index + 1, set, no);
String yes = path + String.valueOf(str[index]);
process2(str, index + 1, set, yes);
}3.打印一个字符串的全部排列
这个有好几种做法:
1、交换递归
public static ArrayList<String> permutation(String str) {
ArrayList<String> res = new ArrayList<>();
if (str == null || str.length() == 0) {
return res;
}
char[] chs = str.toCharArray();
process(chs, 0, res);
return res;
}
public static void process(char[] str, int i, ArrayList<String> res) {
if (i == str.length) {
res.add(String.valueOf(str));
}
for (int j = i; j < str.length; j++) {
swap(str, i, j);
process(str, i + 1, res);
swap(str, i, j);
}
}2、标记法
List<Boolean> flag =null;
List<Integer> path =null;
public List<List<Integer>> permute(int[] num) {
flag = new ArrayList<>();
path = new ArrayList<>();
List<List<Integer>> ans = new ArrayList<>();
for(int i = 0;i<num.length;i++){
flag.add(false);
}
dfs(num,0,ans);
return ans;
}
public void dfs(int[] num,int u,List<List<Integer>> ans){
if(num.length == u){
List<Integer> pathList = new ArrayList<>(path.size());
pathList.addAll(path);
ans.add(pathList);
return;
}
for(int i=0;i<num.length;i++){
if(!flag.get(i)){
flag.set(i,true);
path.add(num[i]);
dfs(num,u+1,ans);
flag.set(i,false);
path.remove(path.size()-1);
}
}
}
4.打印一个字符串的全部排列,要求不要出现重复的排列
//分支限界
public static ArrayList<String> permutationNoRepeat(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 process2(char[] str, int i, ArrayList<String> res) {
if (i == str.length) {
res.add(String.valueOf(str));
}
boolean[] visit = new boolean[26]; // 相当于一个HashMap,保存状态
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, res);
swap(str, i, j);
}
}
}3.从左往右的尝试模型
1.把数字翻译成字符串
剑指 Offer 46
2.背包问题
4.从范围上尝试的模型
给定一个整型数组arr,代表数值不同的纸牌排成一条线, 玩家A和玩家B依次拿走每张纸牌, 规定玩家A先拿,玩家B后拿, 但是每个玩家每次只能拿走最左或最右的纸牌, 玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。
public static int win1(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));
}Java开发工程师面试必知必会 文章被收录于专栏
这里包含Java工程师面试的必会知识
腾讯成长空间 5958人发布