算法Day2:递归——汉诺塔、斐波那契数列

蓝桥杯算法合集: 蓝桥杯算法合集(终极完结版)

递归算法

递归思想
拆解问题:大问题->小问题
求解:小问题->大问题

写递归方法的步骤
①明确函数的功能。先不要去思考里面代码怎么写,首先搞清楚这个函数的干嘛用的,能完成什么功能?
②明确原问题与子问题的关系 。通常是寻找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. 递归实现组合型枚举

【题目描述】

在这里插入图片描述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);
    }
}
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值