递归题目简记

1、汉诺塔问题

1.1、描述:

将 n 个盘子(均在from柱子上,上面的盘子最小,往下依次增大)从 from 盘移动到 to 盘,可以借助中间盘 mid,移动过程中要保证小盘子不能在大盘子之下

1.2、思路:

- 抽象考虑,先将 1~n-1 的盘子从 from 移动到 mid 上,再将 n 从 from 移动到 to 上,再将 1~n-1 的盘子从 mid 移动到 to 上,就完成了。
- 对于 1~i-1 的移动,是由子过程递归调用实现的,每个子问题只管自己的最下面一个和它上面的整体 两个部分。在调用过程中 fromtomid 的实际值也在不断变化。
- baseCase: 只剩一个盘子时,直接将它移动到 to 上即可。

1.3、源码:

public class Hanoi {
public static void main(String[] args) {
int n = 3;
func(n,"左","右","中");
}
public static void func(int n,String from,String to,String mid){
if(n==1){
System.out.println("盘子 "+n+" 从 "+from+" 移动到 "+to);
return;
}
func(n-1,from,mid,to);
System.out.println("盘子 "+n+" 从 "+from+" 移动到 "+to);
func(n-1,mid,to,from);
}
}

1.4、运行结果:

盘子 1 从 左 移动到 右
盘子 2 从 左 移动到 中
盘子 1 从 右 移动到 中
盘子 3 从 左 移动到 右
盘子 1 从 中 移动到 左
盘子 2 从 中 移动到 右
盘子 1 从 左 移动到 右
Process finished with exit code 0

2、获取字符串的所有子集

2.1、描述

给定一个字符串,输出该字符串的所有子集,包括 ""

2.2、思路

很常见的从左到右判断要或不要当前字符的问题,就我浅薄的笔试经验来讲,这类题很容易遇到,一般是动态规划题目,需要优化,直接递归会超时。比如原串是 "abc" 到每一位判断要不要当前字符,全要就是 "abc" ,全不要就是"".

2.3、源码

public class StrSubset {
public static void main(String[] args) {
String str = "abc";
func(str,0,"");
}
public static void func(String str,int index,String res){
if(index == str.length()){
System.out.print(res+" ");
return;
}
//要该位置字符
func(str,index+1,res+str.charAt(index));
//不要该位置字符
func(str,index+1,res);
}
}

2.4、运行结果

abc ab ac a bc b c
Process finished with exit code 0

3、字符串的全排列

3.1、描述

输出给定字符串的全排列,如 "abc" 的全排列:
abc acb bac bca cba cab

3.2、思路

仍是从左到右的模型,但是全排列在于字符顺序的变化而不是要或者不要,即在于:对于每一个字符,要不要和它后面的字符交换位置,第一个字符一直不交换位置则结果还是在第一个位置,如果一直交换那么第一个字符最终就会出现在最后一个位置。对每一个位置的字符都如此。
剪枝去重,对于每一个位置的字符,要想将它与后面的每一个字符换位置,如果前面的字符已经出现过了,比如 aba index=0, 那么 i=2 时,a 和 a 交换就没有意义了。

3.3、源码

public class StrFullArrangement {
public static void main(String[] args) {
String str = "aba";
func(str.toCharArray(),0);
}
public static void func(char[] str,int index){
if(index == str.length){
System.out.println(str);
return;
}
//去重剪枝
boolean[] visitedArr = new boolean[26];
for (int i = index; i < str.length; i++) {
if(!visitedArr[str[i]-'a']){
visitedArr[str[i]-'a'] = true;
swap(str,i,index);
func(str,index+1);
swap(str,i,index);
}
}
}
public static void swap(char[] str, int i, int index){
char temp = str[i];
str[i] = str[index];
str[index] = temp;
}
}

3.4、输出

  • 后者表明达到了去重目的
"abc":
abc acb bac bca cba cab
Process finished with exit code 0
"aba":
aba aab baa
Process finished with exit code 0

4、智者取数

4.1、描述

- 给定一个整型数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。
【举例】
arr=[1,2,100,4]。
开始时,玩家A只能拿走14。如果开始时玩家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。

4.2、思路

先取的人在剩余的范围内尽量取最大的,后取的人只能在剩余的里面先取,但是取得的最小的,因为先取的人会尽量计算
在 L~R 上后手相当于在 L+1~R 或者 L~R-1 上先手,但是只能选结果中较小的,因为是后手。

4.3、源码

public class SmartPersonGetNum {
public static void main(String[] args) {
int[] arr = {1,2,100,4};
System.out.println(Math.max(before(arr,0, arr.length-1),behind(arr,0, arr.length-1)));
}
public static int before(int[] arr,int L, int R){
if(L==R) return arr[L];
int num1 = arr[L]+behind(arr,L+1,R);
int num2 = arr[R]+behind(arr,L,R-1);
return Math.max(num1,num2);
}
public static int behind(int[] arr,int L,int R){
if(L==R) return 0;
int num1 = before(arr,L+1,R);
int num2 = before(arr,L,R-1);
return Math.min(num1,num2);
}
}

5、逆序栈

5.1、描述

不使用额外的数据结构,只使用递归函数,得到逆序的栈。

5.2、思路

-将栈画出来逐步操作,可以更好地理解下。
-获取栈顶元素,递归,再放入栈顶元素,栈中元素的顺序不变, 获取栈底元素,递归,再放入栈底元素,栈元素逆序。
-因此,需要先获得栈底元素,而且获取后,不将该元素压入栈。

5.3、源码

public class ReverseStack {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < 5; i++) {
stack.push(i);
}
reverse(stack);
while (!stack.isEmpty()){
System.out.print(stack.pop() +" ");
}
}
//反转栈
public static void reverse(Stack<Integer> stack){
if(stack.isEmpty()) return;
int temp = func(stack);
reverse(stack);
stack.push(temp);
}
//得到栈底元素,并将上面的元素向下覆盖
public static int func(Stack<Integer> stack){
//获取栈底元素后不将元素重新入栈
int res = stack.pop();
if(stack.isEmpty()) return res;
int last = func(stack);
stack.push(res);
return last;
}
}

5.4、运行结果

0 1 2 3 4
Process finished with exit code 0

6、数字字符串 --> 字母字符串

6.1、描述

有一串数字字符串,按照 '1'-'A' ~ '26'-'Z' 的规则将字符串转化为字母字符串,统计字符串的个数和转换的结果

6.2、思路

从左到右的模型,一次转换一个字符或者两个字符,直到转换完成,将结果存入集合内。
要注意的是,
- 如果当前字符为0,那么就是说没有可以转换的,前面的决定有误,舍弃这种决定,直接返回0。
- 如果当前字符为2,那么下一个字符必须小于7才有对应的数字。
- 另外对于1和2来说,下一个下标位置不能越界。

6.3、源码

public class NumToChar {
static ArrayList<String> list = new ArrayList<>();
static int dis = 'A'-'1';
public static void main(String[] args) {
String str = "11112";
System.out.println(func(str.toCharArray(),0,""));
list.forEach(s -> System.out.print(s+" "));
}
public static int func(char[] chars,int index,String temp){
if(index >= chars.length) {
list.add(temp);
return 1;
}
if(chars[index]=='0') return 0;
int res = func(chars,index+1,temp+(char)(chars[index]+dis));
if(chars[index]=='1' && index+1< chars.length){
res += func(chars,index+2,temp+(char)(Integer.parseInt(""+chars[index]+chars[index+1])+'A'));
return res;
}else if(chars[index]=='2'){
if(index+1 < chars.length && chars[index+1]<='6'){
res += func(chars,index+2,temp+(char)(Integer.parseInt(""+chars[index]+chars[index+1])+'A'));
}
}
return res;
}
}

6.4、运行结果

8
AAAAB AAAM AALB ALAB ALM LAAB LAM LLB
Process finished with exit code 0

7、0/1 背包

7.1、描述

n个物品,重量以此为 wei[i] ,价值依次为 val[i] ,在重量不超过限制 bag 的情况下,获取价值尽量大的价值,并返回价值。

7.2、思路

从左到右的模型,但是不能仅仅是加或者不加,要考虑容量的限制,如果超出容量了,也不能加。

7.3、源码

public class FamousBag {
public static void main(String[] args) {
int[] val = {5,2,4,1,100};
int[] wei = {6,7,5,1,4};
System.out.println(func(val,wei,0,0,10));
}
public static int func(int[] val, int[] weight, int index,int alreadyWei,int bag){
if(alreadyWei > bag) return -1;
if(index >= val.length) return 0;
int val1 = 0;
int temp = func(val, weight, index+1, alreadyWei+weight[index], bag);
//超出容量,不能加
if(temp != -1){
val1 = val[index]+temp;
}
int val2 = func(val, weight, index+1, alreadyWei, bag);
return Math.max(val1,val2);
}
}

7.4、运行结果

105
Process finished with exit code 0

8、N 皇后

8.1、描述

在 NxN 的棋盘上摆放 N 个皇后棋子,在保证两两之间不同行不同列不同一45°倾角斜线的基础上,共有多少种摆放方式。

8.2、思路

对于每一行的每一个位置进行尝试,对于每一个位置进行递归尝试,使用一个 record 数组记录前 i 行的都放在那一列,在进行判断,看是否会产生冲突。同一列时,列相减为0,不能以计算斜率的方式判断,需要单独列出来。

8.3、源码

public class NQueen {
public static void main(String[] args) {
System.out.println(func(8,new int[8],0));
}
public static int func(int n,int[] record,int index){
if(index==n) return 1;
int res = 0;
for (int i = 0; i < n; i++) {
if(illegal(record,index,i)){
record[index] = i;
res += func(n,record,index+1);
}
}
return res;
}
public static boolean illegal(int[] record,int i,int j){
for (int k = 0; k < i; k++) {
if(record[k] == j) return false;
if(Math.abs(i-k) == Math.abs(j-record[k])) return false;
}
return true;
}
}

8.4、运行结果

92
Process finished with exit code 0

8.5、位运算版本

public class QueenNPlus {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int N = in.nextInt();
int limit = N==32 ? -1 : (1<<N)-1;
System.out.println(func(limit, 0, 0, 0));
}
public static int func(int limit,int cloLimit,int leftLimit,int rightLimit){
if(limit == cloLimit) return 1;
int res = 0;
//获取可以放置皇后的位置,可以放置的位置为 1
int pos = limit & (~(cloLimit | leftLimit | rightLimit));
//可放置位置不为全 0
while(pos != 0){
//取得最右边的 1,其他位置为 0
int mostRightOne = pos & (~pos+1);
//可放置位置减去这个 1
pos = pos - mostRightOne;
//继续递归求解
//1、行限制 cloLimit直接 | 上下一个要放置的位置即可,但是在下一行,左右限制要分别左移右移,以满足仍是 45° 倾角的原则
res += func(limit,cloLimit|mostRightOne,
(leftLimit|mostRightOne)<<1,(rightLimit|mostRightOne)>>>1);
}
return res;
}
}
posted @   心是冰冰的  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示