一、前言 暴力递归延伸出了很多常见的算法:动态规划、贪心、回溯等。本篇文章将从它们的基本思想——递归入手,解决一些经典问题。
二、题目
1、汉诺塔问题
相信大家都玩过汉诺塔,简介如下:
文章图片
初始给定三根柱子和一个从上到下依次增大的圆盘堆,初始时圆盘堆放在第一个柱子上,请你给出这些圆盘通过中间柱子为中介,最终达到最后一个柱子所需要的最优路径。(条件是一次只可以移动一个圆盘,并且小圆盘必须放在大圆盘上面。)
分析:这类的题目路径极为复杂,不方便直接看出。但是我们只要想出它的边界条件就可以推出总结果。首先,我们可以将初始柱子上的n-1个移动到中间那个柱子,然后将第n个圆盘移动到最终的柱子, 最后将中间的n-1个柱子移动回最终柱子即可。
是的,它只需要三步,假设我们已经有了一个函数fun(n,start,end),它的作用就是将n个圆盘从start移到end,那么这道题的步骤只有三步:
if n ==1这种递归就可以清晰的解决汉诺塔问题了
直接输出 move 1 from “start” to “end”
else
fun(n-1,"初始",“中间”)//将n-1个移动到中间位置
打印 move n from “初始” to “最终”
fun(n-1,"中间",“最终”)
代码:
// 汉诺塔问题
public static void hanoi(int n){
if(n>0){
fun(n,"左","右","中");
}
}
public static void fun(int i,String start,String end,String other){
if(i==1)
System.out.println("Move 1 from "+start+" to "+end);
else{
fun(i-1,start,other,end);
System.out.println("Move "+i+" from "+start+" to " +end);
fun(i-1,other,end,start);
}
}
2、子序列、子集问题
这类题目分布很广泛,大概就是让你求一个字符串的所有子序列呀,或者一个集合的所有子集之类的。这种题目力扣上有很多,大多都是使用动态规划、贪心、滑动窗口等解决,但是其实他们的本质都是递归,找条件递归。
分析:如果我有一个字符串s,我如何求它的所有子序列呢?首先我将这样思考:
1、字符串不方便计算,我将它转化为char数组我们通过process(chs,0)开始从头遍历字符数组(chs是字符数组),如果取当前字符,就直接process(chs,i+1);如果不取,就先将chs[i]=0,然后再用process(chs,i+1),然后再将chs[i]恢复即可。这个递归的终止条件就是i==chs.length(这其实就类似回溯)。
2、我要求所有子序列,所以我新建一个List保存
3、一个长为n的字符串,每个字符我都有取和不取两种情况可以选择,所以一共2^n个子序列。
4、如果不使用List储存,那么我就要在每遍历完一次字符数组就要输出一次结果
5、为了方便4的输出,如果不取当前字符,就给它置0,完事后再变回来
代码:
// 打印一个字符串包括空字符串在内的所有子序列
public static void PrintSonStr(String s){
char[]chs=s.toCharArray();
process(chs,0,new ArrayList());
}
public static void process(char[]chs, int i, List res){
if(chs.length==i) {
for(char j:chs){
if(j!=0)
System.out.print(j);
}
System.out.println();
return ;
}
process(chs,i+1,res);
char tmp=chs[i];
chs[i]=0;
process(chs,i+1,res);
chs[i]=tmp;
}
3、全排列问题
大家可能经常遇到一类问题,大多是求例如一个字符串的所有可能的排列顺序,其实解决方法还是大同小异。如果我们正在对第i个字符进行判断,那么前面的i-1个字符已经确定,只需要判断j从第i个数开始,到第n个数为止,i需不需要和第j个数交换,如果交换就递归调用,调用结束后再交换回来;还可以通过visit数组记录访问信息,防止重复发生。
代码:
// 全排列问题
public static ArrayList Quanpailie(String s){
ArrayListres=new ArrayList<>();
if(s==null||s.length()==0)
return res;
char []chs=s.toCharArray();
process2(chs,0,res);
return res;
}
public static void process2(char[]str,int i,ArrayListres){
if(i==str.length)
res.add(String.valueOf(str));
boolean[]visit=new boolean[26];
for(int j=i;
j
4、聪明的人选纸牌问题
文章图片
这道题目我们只需要写一个先手函数和一个后手函数就行,如果A在L到R上先取,它可能取左或者右,那么它在(L+1,R)或者(L,R-1)上就是后手取,并且取得两种取法的最大值。同理,如果B在L到R后手取,那么他在(L+1,R)或者(L,R-1)上就是先手取,并且返回两种取法最小值(因为最大值已经被A取走)。
代码:
// 绝顶聪明的人选纸牌问题
public static int XuanZhiPai(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 l,int r){
if(l==r)
return arr[l];
if(l<0||r>=arr.length||l>r)
return 0;
return Math.max(s(arr,l+1,r),s(arr,l,r-1));
}
public static int s(int []arr,int l,int r){
if(l==r)
return arr[l];
if(l<0||r>=arr.length||l>r)
return 0;
return Math.min(f(arr,l+1,r),f(arr,l,r-1));
}
5、栈的原地逆序
给定一个栈,需要你逆序这个栈,但是不可以使用额外的超过常数级别的空间。
代码:
public static void TurnStack(Stack stack){
if(stack.isEmpty())
return;
int i = f1(stack);
reverse(stack);
stack.push(i);
}
public static int f1(Stack stack){
int ans = stack.pop();
if(stack.isEmpty())
return ans;
else{
int last = f1(stack);
stack.push(ans);
return last;
}
}
public static void reverse(Stackstack){
if(stack.isEmpty())
return;
int i = f1(stack);
reverse(stack);
stack.push(i);
}
6、
代码:// 规定1和A对应,2和B对应... // 那么一个数字字符串比如“111”,就可以转化为“AAA”或者“KA”或者“AK” // 给定一个只有数字字符组成的字符串str,返回有多少种转化结果
public static int TurnToEn(String str){
if(str==null||str.length()==0)
return 0;
char[]chs=str.toCharArray();
returnprocess4(chs,0);
}
public static int process4(char[]str,int i){
if(i==str.length)
return 1;
if(str[i]=='0')
return 0;
if(str[i]=='1'){
int res = process4(str,i+1);
if(i+1='0'&&str[i+1]<='6'))
res+=process4(str,i+2);
return res;
}
return process4(str,i+1);
}
7、背包问题
代码:// 给定两个长度都为N的数组weights和values,weights[i]和values[i] // 分别代表第i号物品的重量和价值,给一个正数bag,表示可以装bag的带子,求可以装的最大价值是多少
public static int MaxValue(int bag,int []weights,int []values){
return process5(bag,weights,values,0);
}
public static int process5(int bag,int[]weights,int[]values,int i){
if(bag<=0)
return 0;
if(i>=values.length)
return0;
return Math.max(process5(bag,weights,values,i+1),process5(bag-weights[i],weights,values,i+1)+values[i]);
}
8、N皇后问题
代码:
public static int NQueen(int n){
if(n<1)
return 0;
int []record=new int[n];
return process7(n,record,0);
}
public static int process7(int n,int[]record,int i){
if(i==n)
return 1;
int res = 0;
for(int j=0;
j
三、尾言 代码半原创,有些部分我进行了删改,原创为马士兵教育。
【动态规划|暴力递归经典问题】最后几题没有解析实在是懒了。
推荐阅读
- 人工智能|干货!人体姿态估计与运动预测
- 分析COMP122 The Caesar Cipher
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)
- C语言学习(bit)|16.C语言进阶——深度剖析数据在内存中的存储
- Python机器学习基础与进阶|Python机器学习--集成学习算法--XGBoost算法
- 个人日记|K8s中Pod生命周期和重启策略
- 数据结构与算法|【算法】力扣第 266场周赛
- 数据结构和算法|LeetCode 的正确使用方式
- leetcode|今天开始记录自己的力扣之路
- 人工智能|【机器学习】深度盘点(详细介绍 Python 中的 7 种交叉验证方法!)