斐波那契数列
- 求斐波那契数列矩阵乘法的方法
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) = C1F(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++;
}
}
}