从斐波那契开始优化递归

斐波那契数列:1、1、2、3、5、8、13……

每一项都是前两项之和,要求第 n 项的结果就需要 递归优化 - 图1,那么能不能做进一步优化呢?

对于类似斐波那契数列这样的,除了初始项之外,后续每一项都严格满足某一递归式的问题,都能优化为 递归优化 - 图2

什么是严格满足?就像斐波那契数列一样,除了 1、2 以外其他项都满足 递归优化 - 图3。如果一个递归是按照不同的条件进行不同的递归,就不是一个严格满足某一递归式,就不一定能优化为 递归优化 - 图4,例如下面的代码就不是严格满足:

  1. int getMin(int a, int b, int c) {
  2. int data = 0;
  3. if (a > 2) {
  4. data = getMin(10, 3, 5);
  5. } else if (c > 2) {
  6. data = getMin(2, 3, 45);
  7. } else {
  8. data = -1;
  9. }
  10. return data;
  11. }

也可以这样理解:只要是有递归式的就是严格满足的。如某个问题的解存在如下递归式:递归优化 - 图5,只要后续项都满足该递归式,那么就能被优化为 递归优化 - 图6

以斐波那契数列为例:递归优化 - 图7,是一个二阶行列式问题,则一定存在通用式:递归优化 - 图8
上面的同时是线性代数的知识,有兴趣可以去看看……

将数列带入,就可以得到
递归优化 - 图9
递归优化 - 图10
进一步得出:

递归优化 - 图11
递归优化 - 图12
通过计算得出
递归优化 - 图13

则可以得到斐波那契数列的递归式:
递归优化 - 图14

这样一来,就将第 N 项问题转化为了一个矩阵的 N 次方问题,矩阵的 N 次方算的越快求解就越快

如果对于随便一个数,该数的 N 次方证明算最快呢?

假设求 递归优化 - 图15,如果使用 10 * 10 75 次就能求出,复杂度为递归优化 - 图16
优化为 递归优化 - 图17复杂度:75 的 二进制位 1001011,即递归优化 - 图18,进行如下运算:

  • 递归优化 - 图19,二进制上第 1 位为 1,进行计算
  • 递归优化 - 图20,二进制上第 2 位为 1,进行计算
  • 递归优化 - 图21,二进制上第 3 位为 0,不进行计算
  • 递归优化 - 图22,二进制上第 4 位为 1,进行计算
  • 递归优化 - 图23,二进制上第 5 位为 0,不进行计算
  • 递归优化 - 图24,二进制上第 6 位为 0,不进行计算
  • 递归优化 - 图25,二进制上第 7 位为 1,进行计算

即:递归优化 - 图26 ,通过 7 次计算即可得出

总体思路:先将指数变为二进制,然后通过二进制的每一位来判断是否需要将该次幂进行计算,通过二进制的长度次计算即可得到。如:递归优化 - 图27,通过 5 次计算就能得到结果。

同样的方法求一个矩阵的幂 递归优化 - 图28 只需要求出 递归优化 - 图29递归优化 - 图30递归优化 - 图31递归优化 - 图32递归优化 - 图33递归优化 - 图34递归优化 - 图35 即可然后判断 75 对应的二进制位上是否是 1 ,再乘上单位矩阵即可。

斐波那契数列实现:

public class Fibonacci {

    private static int fi(int n) {
        if (n <= 0) return -1;

        if (n == 1 || n == 2) return 1;

        int[][] base = {
                {1, 1},
                {1, 0}
        };

        int[][] res = matrixPower(base, n - 2);

        return res[0][0] + res[0][1];
    }

    private static int[][] matrixPower(int[][] m, int p) {
        int[][] res = new int[m.length][m[0].length];

        // 单位矩阵
        for (int i = 0; i < m.length; i++) {
            res[i][i] = 1;
        }

        int[][] tmp = m;
        for (; p != 0; p >>= 1) {
            if ((p & 1) == 1) {
                res = muliMatrix(res, tmp);
            }
            tmp = muliMatrix(tmp, tmp);
        }

        return res;
    }

    // 矩阵相乘
    private static int[][] muliMatrix(int[][] m1, int[][] m2) {
        int[][] res = new int[m1.length][m2[0].length];
        for (int i = 0; i < m1.length; i++) {
            for (int j = 0; j < m2[0].length; j++) {
                for (int k = 0; k < m2.length; k++) {
                    res[i][j] += m1[i][k] * m2[k][j];
                }
            }
        }
        return res;
    }

    public static void main(String[] args) {
        for (int i = 1; i < 30; i++) {
            System.out.println(fi(i));
        }
    }
}

再来看一个比较复杂的:递归优化 - 图36,最少需要前面 5 项才能得到后续结果,这是个 5 接行列式通用式为:
递归优化 - 图37,可以用同样的方式来优化时间复杂度。

具体例子:
农场对种牛的生育进行控制,农场一开始只有一只种牛,每年每只种牛都能生一只小种牛,小种牛需要 3 年后才能生新的种牛,请问,第 N 年农场有多少只种牛?

  • 第一年,只有A, 共 1 只
  • 第二年,A 生 B, 共 2 只
  • 第三年,A 生 C,现在有种牛 A、B、C 共 3 只
  • 第四年,A 生 D,现在有种牛 A、B、C 、D共 4 只
  • 第五年,A 生 E、B 生 F,现在有种牛 A、B、C 、D、E、F 共 6 只
  • 第六年,A 生 G、B 生 H、C 生 I,现在有种牛 A、B、C 、D、E、F、G、H、I 共 9 只
  • ……
  • 第 N 年,递归优化 - 图38

例子:达标字符串

字符串只由 ‘0’ 和 ‘1’ 两种字符构成
当字符串长度为 1 时,所有可能的字符串为 “0”、”1”;
当字符串长度为 2 时,所有可能的字符串为 “00”、”01”、”10”、”11”;
当字符串长度为 3 时,所有可能的字符串为 “000”、”001”、”010”、”011”、”100”、”101”、”110”、”111”……
如果某一个字符串中,只要是出现 ‘0’ 的位置,左边就靠着 ‘1’,这样的字符串叫作达标字符串。
给定一个正数 N,返回所有长度为 N 的字符串中,达标字符串的数量。
比如,N=3,返回 3,因为只有 “101”、”110”、”111” 达标。

思路一:
递归每个位置都有可能是 0、1 然后判断组成字符串是否合法

public static int getNum1(int n) {
    return process(n, "");
}

private static int process(int n, String str) {
    if (n == 0) {
        return check(str) ? 1 : 0;
    }

    return process(n - 1, str + "0") + process(n - 1, str + "1");
}

private static boolean check(String str) {
    char[] chars = str.toCharArray();
    if (chars[0] == '0') return false;
    for (int i = 1; i < chars.length; i++) {
        if (chars[i] == '0' && chars[i - 1] == '0') return false;
    }
    return true;
}

思路二:
递归改动态规划

public static int getNum2(int n) {
    if (n < 1) return -1;
    if (n == 1) return 1;
    if (n == 2) return 2;

    int[] dp = new int[n];
    dp[0] = 1;
    dp[1] = 2;

    for (int i = 2; i < n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }

    return dp[n - 1];
}

思路三:
打表法

public static int getNum3(int n) {
    if (n < 1) return -1;
    if (n == 1) return 1;
    if (n == 2) return 2;

    int first = 1;
    int second = 2;

    int res = 0;
    for (int i = 2; i < n; i++) {
        res = first;
        first = second;
        res += first;
        second = res;
    }

    return res;
}

思路四:
矩阵行列式规律

public static int getNum4(int n) {
    if (n < 1) {
        return 0;
    }
    if (n == 1 || n == 2) {
        return n;
    }
    int[][] base = {{1, 1}, {1, 0}};
    int[][] res = matrixPower(base, n - 2);
    return 2 * res[0][0] + res[1][0];
}

public static int[][] matrixPower(int[][] m, int p) {
    int[][] res = new int[m.length][m[0].length];
    for (int i = 0; i < res.length; i++) {
        res[i][i] = 1;
    }
    int[][] tmp = m;
    for (; p != 0; p >>= 1) {
        if ((p & 1) != 0) {
            res = muliMatrix(res, tmp);
        }
        tmp = muliMatrix(tmp, tmp);
    }
    return res;
}

public static int[][] muliMatrix(int[][] m1, int[][] m2) {
    int[][] res = new int[m1.length][m2[0].length];
    for (int i = 0; i < m1.length; i++) {
        for (int j = 0; j < m2[0].length; j++) {
            for (int k = 0; k < m2.length; k++) {
                res[i][j] += m1[i][k] * m2[k][j];
            }
        }
    }
    return res;
}