蓝桥杯算法合集: 蓝桥杯算法合集(终极完结版)
递归算法
递归思想
拆解问题:大问题->小问题
求解:小问题->大问题
写递归方法的步骤
①明确函数的功能。先不要去思考里面代码怎么写,首先搞清楚这个函数的干嘛用的,能完成什么功能?
②明确原问题与子问题的关系 。通常是寻找f(n) 与f(n –1) 的关系有时规模还要缩小一些f(n)与f(n-2)等 取决于做题经验
③明确递归基(边界条件)。在递归的过程中,子问题的规模在不断减小,当小到一定程度时可以直接得出它的解寻找递归基,相当于是思考:问题规模小到什么程度可以直接得出解?
java递归模板:
data type recursion(level,param1,param2){
//1.recursion terminator 递归终止的条件/递归基
if(level<min_level){
procss_result;
return;
}
//2.process logic in current level 处理当层逻辑
process(level,data...)
//3.drill down 深挖下一层
self.recursion(level-1,p1,.....)
//4.reverse the current level states if needed
例如
求前1~n之和
1+2+……+n
int sum(int n){
//n<=1为recursion terminator
if(n<=1) {
return n;
}
//n为process logic in current level
//sum(n-1)为drill down
return n+sum(n-1)
}
分析复杂度
时间复杂度:O(n)
计算如下:
T(n)=T(n-1)+O(1)
而T(n-1)=T(n-2)+O(1)
故
T(n)=(T(n-2)+O(1))+O(1)
=……
……
=T(n-(n-1)))+(n-1)*O(1)
=T(1)+(n-1)*O(1)
=O(n)
空间复杂度:O(n) 因为递归次数为规模次数n
关于递归算法的空间复杂度计算通用公式:递归深度*辅助空间 O(1)
常见递推式的时间复杂度
递归转非递归:
(1)自己维护栈来保存参数和局部变量
(2)重复使用一组相同变量来存储每个栈帧的内容
java中存在尾递归不存在尾调用
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用栈帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用栈帧就可以了。
斐波那契数列
有一对兔子,从出生后第3个月起,每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子。
假如兔子都不死,求第n个月兔子对数
关于斐波那契数列的兔子繁殖问题其实如下:
实际月份 1 2 3 4 5 6 7 8
幼仔对数 1 0 1 1 2 3 5 8
成兔对数 0 1 1 2 3 5 8 13
总体对数 1 1 2 3 5 8 13 21
幼仔对数=前月成兔对数
成兔对数=前月成兔对数+大前月幼仔对数
总体对数=本月成兔对数+本月幼仔对
package 递归;
public class 斐波那契数列 {
//上楼梯的递推式与斐波那契数列一样也为f(n)=f(n-1)+f(n-2)
//n fib(n)
//1 1 1 2 3
//2 1 2 3 5
//3 2
//4 3
//5 5
/**递归
* 存在重复计算
* @param n
* @return
* 时间复杂度:O(2^n) 尝试按照T(n)=T(n-1)+T(n-2)+O(1)去推 失败了
* 空间复杂度:对应搜索二叉树 O(n)
*/
int fib0(int n) {
if(n<=2) return 1;
return fib0(n-2)+fib0(n-1);
}
/**
* @param n
* @return
* 用数组fib存储计算过的fib(n)值 每一个值对应的fib(n)只被计算一次
* 时间复杂度:O(n)
* 空间复杂度:O(n)
*/
int fib1(int n) {
//规模小于2直接返回值
if(n<=2) return 1;
int [] nums=new int[n+1];
return fib1(n,nums);
}
/**
* @param n
* @param nums
* @return
* 空间复杂度:O(n)
* 时间复杂度:O(n)
*/
int fib1(int n,int []nums) {
if(n<=2) return 1;//递归出口
if(nums[n]==0) {//说明n对应的fib值没有计算过
nums[n]=fib1(n-1,nums)+fib1(n-2,nums);
}
return nums[n];
}
int fib2(int n) {
if(n<=2) return 1;
int []fib=new int[n+1];
fib[1]=fib[2]=1;
for(int i=3;i<=n;i++) {
fib[i]=fib[i-1]+fib[i-2];
}
return fib[n];
}
//0// 1 1 3 3
//1// 1 2 2 5
/**
* @param n
* @return
* 每次运算只需用到数组中的两个元素 所以使用滚动数组
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*
*/
int fib3(int n) {
if(n<=2) return 1;
int []fib=new int[n];
fib[0]=fib[1]=1;
for(int i=3;i<=n;i++) {
//i=3 1 0 1
//4 0 1 0
//5 1 0 1
fib[i&1]=fib[(i-1)&1]+fib[(i-2)&1];
}
return fib[n&1];
}
//两变量
int fib4(int n) {
if(n<=2) return 1;
int first=1;
int second=1;
for(int i=3;i<=n;i++) {
// f(1)+f(2)
first=first+second; //2 3 5
second=first-second; //1 2
}
return first;
}
//三变量
int fib5(int n) {
if(n<=2) return 1;
int first=1;
int second=1;
int third=0;
for(int i=3;i<=n;i++) {
third=first+second;
first=second;
second=third;
}
return third;
}
public static void main(String[] args) {
斐波那契数列 a=new 斐波那契数列();
System.out.println(a.fib0(6));
System.out.println(a.fib1(6));
System.out.println(a.fib2(6));
System.out.println(a.fib3(6));
System.out.println(a.fib4(6));
System.out.println(a.fib5(6));
}
}
汉诺塔
从左到右有A、B、C三根柱子,其中A柱子上面有从小叠到大的n个圆盘,
现要求将A柱子上的圆盘移到C柱子上去,期间只有一个原则:
一次只能移到一个盘子且大盘子不能在小盘子上面,求移动的步骤和移动的次数
package 递归;
public class 汉诺塔 {
/**hanoi(n,p1,p2,p3) 表示n个碟子从p1柱子借助p2移动到p3
* @param n
* @param p1
* @param p2
* @param p3
* 时间复杂度:
* T(n)=T(n-1)*2+O(1)
* =(T(n-2)*2 + O(1))*2+O(1)=T(n-2)*2^2+O(1)*2^1+O(1)*2^0
* =(T(n-3)*2+O(1))*2^2+O(1)*2^1+O(1)*2^0
* =T(n-3)*2^3+O(1)*(2^2+2^1+2^0)
* =T(n-(n-1))*2^(n-1)+O(1)*(2^(n-2)+……+2^2+2^1+2^0)
* =2^n-1
*
* T(n-1)=T(n-2)*2+O(1)
* T(n-2)=T(n-3)*2+O(1)
* 空间复杂度:O(n)
*/
void move(int n,String begin,String end) {
System.out.println("第"+n+"个碟子从"+begin+"移动到"+end);
}
void hanoi(int n,String p1,String p2,String p3){
if(n==1) {//递归出口
move(n,p1,p3);
return;
}
//将n-1个碟子从p1移动到p2
hanoi(n-1,p1,p3,p2);
//此时p1只有1个碟子 可操作 那么将该号这个碟子移动到p3
move(n,p1,p3);
//再将这n-1个碟子从p2移动到p3 就完成对全部碟子移动p3的操作
hanoi(n-1,p2,p1,p3);
}
public static void main(String[] args) {
汉诺塔 a=new 汉诺塔();
a.hanoi(8, "A", "B", "C");
}
}
AcWing 92. 递归实现指数型枚举
【题目描述】
AcWing 92. 递归实现指数型枚举
【思路】
每个数有两种选择,选和不选
import java.util.Scanner;
class Main{
static int N = 16;
static int n;
static int st[] = new int[N];//保存每个位置当前的状态,0表示未考虑 1表示选 2表示不选该分支
public static void dfs(int u){
if(u > n){
for(int i = 1; i <= n; i ++)
if(st[i] == 1)
System.out.print(i + " ");
System.out.println();
return;
}
//不选该分支
st[u] = 2;
dfs(u + 1);
st[u] = 0;
//选该分支
st[u] = 1;
dfs(u + 1);
st[u] = 0;
}
public static void main(String args[]){
Scanner reader = new Scanner(System.in);
n = reader.nextInt();
dfs(1);
}
}
输出所有方案写法
import java.util.Scanner;
import java.util.*;
class Main{
static int N = 16;
static int n;
static int st[] = new int[N];//保存每个位置当前的状态,0表示未考虑 1表示选 2表示不选该分支
static List<List<Integer>> ans = new ArrayList<>();
public static void dfs(int u){
if(u > n){
List<Integer> sub = new ArrayList<>();
for(int i = 1; i <= n; i ++)
if(st[i] == 1)
sub.add(i);
ans.add(sub);
return;
}
//不选该分支
st[u] = 2;
dfs(u + 1);
st[u] = 0;
//选该分支
st[u] = 1;
dfs(u + 1);
st[u] = 0;
}
public static void main(String args[]){
Scanner reader = new Scanner(System.in);
n = reader.nextInt();
dfs(1);
for(List sub: ans){
for(int i = 0; i < sub.size(); i ++)
System.out.print(sub.get(i)+" ");
System.out.println();
}
}
}
AcWing 94. 递归实现排列型枚举
【题目描述】
AcWing 94. 递归实现排列型枚举
【思路】
按照依次枚举每个位置放哪个数的顺序
import java.util.Scanner;
public class Main{
static int n;
static int N = 10;
static boolean used[] = new boolean[N]; //表示每个数字是否被使用过
static int f[] = new int[N]; //0表示未填 1~n表示填该数
//递归填第u个坑可以填哪个数字
public static void dfs(int u){
if( u > n){
for(int i = 1; i <= n; i ++)
System.out.print(f[i]+" ");
System.out.println();
return;
}
//枚举每一个数字
for(int i = 1; i <= n; i ++){
if(!used[i]){
used[i] = true;
f[u] = i;
dfs(u + 1);
used[i] = false;
f[u] = 0;
}
}
}
public static void main(String args[]){
Scanner reader = new Scanner(System.in);
n = reader.nextInt();
dfs(1);
}
}
AcWing 93. 递归实现组合型枚举
【题目描述】
【思路】
n个数填m个坑
加入start限制 保证升序选择 即每一位比上一位大 这样就可以实现去重
import java.util.Scanner;
public class Main{
static int n, m;
static int []f;
//当前填第u个坑 当前坑的开始数字从start开始
public static void dfs(int u, int start){
if( u == m){
for(int i = 0; i < m; i ++)
System.out.print(f[i]+" ");
System.out.println();
}
for(int i = start; i <= n; i ++){
f[u] = i;
dfs(u + 1, i + 1);
}
}
public static void main(String args[]){
Scanner reader = new Scanner(System.in);
n = reader.nextInt();
m = reader.nextInt();
f = new int[n];
dfs(0, 1);
}
}
带分数
*/
/*
形如 x = c + a / b的带分数可以化为
b(x - c) = a
易知:a的位数大于等于b,c的位数小于等于x(n)
枚举a的位数(i) 范围[1, 6]
b的位数(j) 范围[1, min(9 - i - 1, 4)]
*/
import java.util.Scanner;
class Main{
static int x;
static int ans;
static int N = 9;
static int f[] = new int[N];
static int cnt = 0;
static boolean used[] = new boolean[N + 1];
public static int get(int l, int r){
int res = 0;
for(int i = l ; i < r; i ++)
res = res *10 + f[i];
return res;
}
public static void dfs(int u){
if( u == N){
//枚举
for(int i = 1; i <= 6; i ++ )//枚举a的位数
for(int j = 1; j <= Math.min(j, 9 - i -1); j ++ ){//枚举b的位数
int a = get(0, i);
int b = get(i, i + j);
int c = get(i + j, 9);
if( b * (x - c) == a ) ans ++;
}
return;
}
for(int i = 1; i <= N; i ++){
if(!used[i]){
f[u] = i;
used[i] = true;
dfs(u + 1);
used[i] = false;
}
}
}
public static void main(String args[]){
Scanner reader = new Scanner(System.in);
x = reader.nextInt();
dfs(0);
System.out.println(ans);
}
}