递归

什么是递归

一种计算过程,如果其中每一步都要用到前一步或前几步的结果,称为递归的。用递归过程定义的函数,称为递归函数,例如连加、连乘及阶乘等。凡是递归的函数,都是可计算的,即能行的 。

古典递归函数,是一种定义在自然数集合上的函数,它的未知值往往要通过有限次运算回归到已知值来求出,故称为“递归”。它是古典递归函数论的研究对象

简单来说就是一种反复调用自己来求解的一种算法。

递归题目

先写一些简单的题目来入门

题一:

有一对刚出生的兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数。

解题思想:

这是一道经典的递归算法题,但是我们可以不用递归的思想进行描述该题目,

我们建立一个二维数组第一个数字为成熟的兔子,第二个数字为未成熟的兔子。

public static int find(int month){
  //定义的number二维数组 第一个数字为成熟的兔子
  //number的第二个数字为未成熟的兔子
  int number[][] = new int[2][100];

  //先定义初始的兔子
  number[0][0] = 0;
  number[1][0] = 1;

  number[0][1] = 0;
  number[1][1] = 1;

  number[0][2] = 1;
  number[1][2] = 1;

  for(int i =3;i<month ;i++){
      //已成熟兔子上两次已成熟的兔子之和,这里的第一个为上上个月成熟的兔子,因为这个时候成熟的兔子产下的新兔子已经成熟,第二个数字为上次的已成熟的兔子
      number[0][i]=number[0][i-2]+number[0][i-1];
      //新出生的兔子为上次的老兔子和上次的新兔子
      number[1][i]=number[0][i-1]+number[1][i-1];
  }
  //返回所有的兔子
  return number[0][month-1]+number[1][month-1];
}

得到的结果为

1 1 2 3 5 8 13 21 34 55

我们可以查看规律,第三个月开始,每个月的兔子数量为上两个月的和

所以我们可以用递归写出新的算法

public static int find2(int month){
    if(month==1 || month==2){
        return 1;
    }
    return find2(month-1)+find2(month-2);
}


题二:

java兔子生兔子递归 递归 兔子_递归

这也是一道基础且经典的算法题,解题思路也同样简单,但是还是有一些缺陷的,使用递归算法会相当耗时。

static int temp = 0;
public static void find(int n){
    if(n<0)return ;
    if(n==0)temp+= 1;
    find(n - 2);
    find(n -1);
}

所以我们可以使用斐波拉契的算法来解题

我们知道青蛙在上一个台阶的时候只会有两种方法来到这个台阶,一种是爬一个楼梯到这里,还有一种方法就是爬两个台阶到这里,所以到现在这个台阶的方法就是上一个台阶的总和 加上 上两个台阶的总和。

public static int find(int n){
    int a = 1;
    int b = 2;
    int sum = a+b;
    //循环n-1是因为从0开始
    for(int i = 0 ; i<n-1 ;i++){
        a = b;
        b = sum;
        sum = a+b;
    }
    //a才是当前台阶的总和
    return a;
}


java兔子生兔子递归 递归 兔子_java兔子生兔子递归_02

这个题目的整体思路就是,首先求出总和,排除掉总和%k不等于0的集合,并找到最大的值。

然后开始进行算法,首先准备一个长度为k的数组,其中每个值都总和除以k,从最大值开始循环找另一个值,如果他们相加大于总和/k,那么进行排除,开始进行下一个,如果加起来小于总和/k,那么就加起来再开始找下一个,如果一直找下一个都找不到,那么就开始下一个数字,如果这个数字找不到任何匹配都无法满足总和除以k那么就可以返回false了。

private boolean backtracking(int[] nums, int k, int target, int cur, int start, boolean[] used) {
    // 返回条件
    if (k == 0) return true;
    if (cur == target) {
        // 构建下一个集合
        return backtracking(nums, k-1, target, 0, 0, used);
    }
    for (int i = start; i < nums.length; i++) {
        if (!used[i] && cur+nums[i] <= target) {
            used[i] = true;
            if (backtracking(nums, k, target, cur+nums[i], i+1, used)) return true;
            used[i] = false;
        }
    }
    return false;
}

public boolean canPartitionKSubsets(int[] nums, int k) {
    // 注意nums[i] > 0
    int sum = 0, maxNum = 0;
    for (int i = 0; i < nums.length; i++) {
        sum += nums[i];
        if (maxNum < nums[i]) maxNum = nums[i];
    }
    if (sum % k != 0 || maxNum > sum/k) return false;
    boolean[] used = new boolean[nums.length];
    return backtracking(nums, k, sum/k, 0, 0, used);
}


我们来看一道经典的算法题

java兔子生兔子递归 递归 兔子_递归_03

class Solution {
       public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        hanoi(A.size(), A, B, C);
    }

    public void hanoi(int n, List<Integer> A, List<Integer> B, List<Integer> C){
        
        if(n == 1){
            C.add(A.get(A.size() - 1));
            A.remove(A.size() - 1);
        }else{
            //把A经过辅助C放到B上
            hanoi(n - 1, A, C, B);
            //把A放到C上
            C.add(A.get(A.size() - 1));
            A.remove(A.size() - 1);
            //把B经过辅助A放到C上
            hanoi(n - 1, B, A, C);
        }
    }
}

你后面可以再回头看看这个题目,我就不写题解了,因为这个题必须要反复去阅读才能理解,一段时候后不去理解,全都忘了


java兔子生兔子递归 递归 兔子_sed_04


这个题目看起来比较简单,其实和台阶问题属于同一类问题,但是为什么我要放在这里,是因为这个题目是不可以用递归进行解题的,会造成超时问题,所以这里我们用两种解法来解

法一:递归

Set set = new TreeSet<Integer>();
public static void test(int temp,int s,int l,int sum){
    if(temp==0){
        tree.add(sum);
    }else {
        test(temp-1,s,l,sum+s);
        test(temp-1,s,l,sum+l);
    }
}

法二:简化递归

我们可以把递归简化以下

public int[] divingBoard(int shorter, int longer, int k) {
    // 木板数量小于1,无解
    if (k < 1) {
        return new int[]{};
    }
    // 两块板子长度一样只有一种解
    if (shorter == longer) {
        return new int[]{shorter * k};
    }
    int[] res = new int[k + 1];
    // 枚举较长木板的数量,那么(k-m)即是短板的数量
    for (int i = 0; i <= k; i++) {
        res[i] = longer * i + shorter * (k - i);
    }
    return res;
}


java兔子生兔子递归 递归 兔子_递归_05

这个算法比较简单,用了递归,但是某些测试用例是无法通过的,所以也记录下来

public static double pow(int n,double x,double temp){
    if(n==0){
        return x;
    }else {
        return pow(n-1,x*temp,temp);
    }
}
public static double depow(int n,double x,double temp){
    if(n==1){
        return x;
    }else {
        return  pow(n-1,x/temp,temp);
    }
}

这个算法没搞明白,以后有机会再看看

public static double myPow(double x, int n) {
    if(n == 0) return 1;
    if(n == 1) return x;
    if(n == -1) return 1 / x;
    double half = myPow(x, n / 2);
    double mod = myPow(x, n % 2);
    System.out.println(half+"  "+mod);
    return half * half * mod;
}