数据结构与算法学习笔记(训练营一第二节)---菲波那切数列

斐波那契数列

  • 求斐波那契数列矩阵乘法的方法
    1)斐波那契数列的线性求解(O(N))的方式非常好理解;
    2)同时利用线性代数,也可以改写出另一种表示:
    | F(N) , F(N-1) | = | F(2), F(1) | * 某个二阶矩阵的N-2次方;
    3)求出这个二阶矩阵,进而最快求出这个二阶矩阵的N-2次方;
  • 类似斐波那契数列的递归优化
    如果某个递归,除了初始项之外,具有如下的形式
    F(N) = C1 * F(N) + C2 * F(N-1) + … + Ck * F(N-k) ( C1…Ck 和k都是常数)并且这个递归的表达式是严格的、不随条件转移的。

那么都存在类似斐波那契数列的优化,时间复杂度都能优化成O(logN)

  • 斐波那契数列可用递归,动态规划,求解做到O(logn),但是用快速幂可以做到O(logn)。
    1.快速幂:
    比如求a^75次方,一般方法a连续相乘75次,这样就太慢了。我们可以这样计算,幂指数75的二进制形式是1001011,定义一个变量res = 1,一个t变量 t= a1,从如果此时幂指数二进制位最右边是1那么把t和res相乘,如果不是一那么不进行相乘计算,且此时让t和t相乘得到第二轮的t,如此往复当幂指数为0时就得到了a75次方。
    2.斐波拉切数列的递推式:|F(n).F(n-1)=|F(2).F(1)||矩阵|^n-2
    3.推广:F(n) = C1
    F(n) +C2F(n-1) +...+CzF(n-k) C1、C2、Cz..和k都是常数,则都有O(logn)的解。
public class Fbo {

    // 递归
    public static int f(int n){
        if(n <= 0){
            return 0;
        }

        if(n <= 2){
            return 1;
        }

        return f(n - 1) + f(n - 2);
    }

    // 动态规划
    public static int dp(int n){
        if(n <= 0){
            return 0;
        }

        if(n <= 2){
            return 1;
        }

        int one = 1;
        int two = 1;
        int cur = 0;
        for (int i = 2; i < n; i++) {
            cur = one + two;
            one = two;
            two = cur;
        }
        return cur;
    }

    // 快速幂方法
    // 有线性代数德 => |Fnfn-1| = |F2F1|*(矩阵)^n-k(k=2)
    public static int quickM(int n){
        if(n <= 0){
            return 0;
        }
        if(n <= 2){
            return 1;
        }
        int[][] matrix = new int[][]{{1,1},{1,0}};
        int[][] ints = matrixPower(matrix, n - 2);

        return ints[0][0] + ints[1][0];

    }

    // 求矩阵的n次幂
    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;
        }

        // res = 矩阵中的1
        int[][] tmp = m;// 矩阵1次方
        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;
    }
    public static void main(String[] args) {
        System.out.println(f(8));
        System.out.println(dp(8));
        System.out.println(quickM(8));
    }
}

  • 一个人可以一次往上迈1个台阶,也可以迈2个台阶返回这个人迈上N级台阶的方法数。
    一阶台阶有一种方法,迈一步,二阶台阶有两种方法两次一步,一次两步..=> f(1) = 1,f(2) = 2... =>n阶台阶f(n) = f(n-1)+f(n-2),就是斐波拉切数列变形,只是第二项的值不一样是二。

  • 第一年农场有1只成熟的母牛A,往后的每年:
    1)每一只成熟的母牛都会生一只母牛
    2)每一只新出生的母牛都在出生的第三年成熟
    3)每一只母牛永远不会死
    返回N年后牛的数量
    递推式:f(0) = 1,f(1) = 2,f(2) = 3,f(3) = 4,=> f(n) = f(n-1)+ f(n-3),是一个三阶的问题矩阵是一个三阶矩阵。

  • 给定一个数N,想象只由0和1两种字符,组成的所有长度为N的字符串如果某个字符串,任何0字符的左边都有1紧挨着,认为这个字符串达标
    返回有多少达标的字符串。
    定义一个函数f(i),表示在i的前面一定是1返回此时i个数达标的情况,
    情况一如果i位置是0那么i+1职位只可能是1,所以让i+2位置返回多少种方法f(i-2),情况二如果i位置是1那么让i+1位置去决策方法数f(i+1)
    所以地推式:f(n) = f(n+1)+f(n+2),又是一个斐波拉切数列问题。

蓄水池算法

解决的问题:
假设有一个源源吐出不同球的机器,只有装下10个球的袋子,每一个吐出的球,要么放入袋子,要么永远扔掉如何做到机器吐出每一个球之后,所有吐出的球都等概率被放进袋子里。
流程:先10个球直接进入袋子里面,然后当后面的球继续进入袋子的时候,让这个球以球的编号10/k(f函数,f(k)1-k等概率返回一个数,返回1-10进袋子)的概率进入袋子中,并且让袋子中的一个且等概率随机的被抛出袋子。这样所有球进入袋子的概率都相等。

/**
 * 假设有一个源源吐出不同球的机器,
 * 只有装下10个球的袋子,每一个吐出的球,要么放入袋子,要么永远扔掉
 * 如何做到机器吐出每一个球之后,所有吐出的球都等概率被放进袋子里
 */
public class Reservoir {
    // 袋子
    private static int[] bag;
    private static int count;
    private static int n ;
    public Reservoir(int n){
        // 用户需要的n大小的袋子所有进入袋子的概率都是 n/num,如果机器吐出球的数量num,
        this.bag = new int[n];
        this.count = 0;
        this.n = n;
    }
    public static void add(int a){
        if(count < n){
            bag[count] = a;
        }else if(rand(count+1) <= n){
            // 随机淘汰一个
            bag[rand(n)-1] = a;
        }
        count ++;
    }
    // 一个随机函数输入i返回1 ~ i的数字
    private static int rand(int i){
        return (int)(Math.random() * i) + 1;
    }
}

随机函数

  • 给定一个随机源函数f可以返回17,设计一个结构可以返回110。
    利用给定随机源生成一个 等概率返回0,1利用二进制返回需要范围数字。设计g函数,比如17,调用f返回123,那么代表0,返回456,那么代表1,返回7无效重新随机,110需要四位二进制,调用四次g函数,得到四位二进制,值的范围在015,如果生的数不是110中的数,那么重新调用g生成,否则返回值,那么就可以等概率返回1~10了。
/**
 * 利用1到7随机函数,设计1~10的随机函数
 */
public class OneToTenRandom {
    public static int random(){
        int g;
        while (true){
            g = g();
            if(g == 0 || g >=11){
                continue;
            }
            break;
        }
        return g;
    }

    private static int g(){
        int res = 0;
        int count = 0;
        while (count < 4){
            int temp = oneToSevenRandom();
            if(temp>=1 && temp <=3 ){
                res |= 0;
            }else if(temp >= 4 && temp <= 6) {
                res |= 1;
            }else{
                continue;
            }
            count++;
            if(count < 4){
                res <<= 1;
            }

        }
        return res;
    }
    //1~7随机生成函数
    private static int oneToSevenRandom(){
        return (int)(Math.random() * 7) + 1;
    }
    public static void main(String[] args) {
        int[] arr = new int[11];
        for(int i = 0;i < 1000000;i++){
            arr[random()]++;
        }
        int count = 0;
        for(int t : arr){
            System.out.println("count:"+count+"=>" +t);
            count++;
        }
    }
}
  • 已知函数f返回0的概率是p,返回1的概率是1-p,设计一个随机函数等概率返回1~10。
    调用2次函数f,则返回的可能,可能的结果是 01 ,11 ,10 ,00
    01 的概率 p(1-p),11的概率(1-p)^2,10的概率p(1-p),00的概率p^2,
    由于01和10的概率是相等的,我们利用这两可以等概率获取1和0,的到01代表1,的到10代表0,那么我们获取到四位二进制就可以代表一个0~15的数了,每次只返回满足条件的数就可以了。
public class P {
    // 等概率返回1 ~ 10
    public static int random2(){
        int count = 0;
        int res = 0;
        while (true){
            while (count < 4){
                int g = g();
                res |= g;
                if(count < 3){
                    res <<= 1;
                }
                count ++;
            }
            if(res == 0 || res >=11){
                res = 0;
                count = 0;
                continue;
            }
            break;
        }
       return res;
    }



    // g函数等概率返回1 和 0
    private static int g(){
        int f;
        int f2;
        while (true){
            f = f();
            f2 = f();
            if((f == 0 && f2 ==0)
                    || (f == 1 && f2 == 1)){
                continue;
            }
            break;
        }
        return (f == 0 && f2 == 1) ? 1 : 0;
    }

    // 返回0的概率是p,返回1的概率是1-p,
    // 假设p是0.3
    private static int f(){
        // 调用h函数如果返回1~3那么返回0,如果返回4~10 返回1
        int res;
        if(h()>=1 && h()<=3){
            res = 0;
        }else{
            res = 1;
        }
        return res;
    }

    // 等概率返回1 ~ 10
    private static int h(){
        return (int)(Math.random() * 10)+1;
    }
    public static void main(String[] args) {
        int[] arr = new int[11];
        for(int i = 0;i < 100000;i++){
            arr[random2()]++;
        }
        int count = 0;
        for(int t : arr){
            System.out.println("count:"+count+"=>" +t);
            count++;
        }
    }
}

你可能感兴趣的:(数据结构与算法学习笔记(训练营一第二节)---菲波那切数列)