快速幂
斐波那契数列的递推式是严格的递归式,不随条件转移的,那么就有如下线性代数的解
接下来就可以求出a、b、c、d了
所以这个行列式就求出来了,接下来就来验证一下是不是每次都是这个行列式
看来是的。
最终,
接下来的问题也就是一个矩阵的n次方能算得有多快整体就有多快了
快速幂 我们先看一个数的n次方怎么算得尽量快,举个例子比如10^75
所以t总共乘了几次呢? 最多log2 75
所以矩阵也是一样
矩阵乘法就是第一个矩阵的行跟第二个矩阵的列对应元素相乘最后相加,
2 1 + 10 + 0*0就是结果中的左上角
// 递归
public static int f1(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
return f1(n - 1) + f1(n - 2);
}
// O(n)解法
public static int f2(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
int ppre = 1;
int pre = 1;
int cur = 0;
for (int i = 3; i <= n; i++) {
cur = ppre + pre;
ppre = pre;
pre = cur;
}
return cur;
}
// logn解法
public static int f3(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return 1;
}
int[][] base = {
{1, 1},
{1, 0}};
int[][] res = matrixPower(base, n - 2);
return res[0][0] + res[1][0];
}
private 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[][] t = m; // 矩阵的1次方
for (; p != 0; p >>= 1) {
if ((p & 1) != 0) {
res = muliMatrix(res, t);
}
t = muliMatrix(t, t);
}
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;
}
扩展
只要C1~Cz,和k都是常数,它们就有logN的解
K为几就是几阶问题
奶牛问题
只有母牛没有公牛,一个农场第一年只有一头母牛,每只母牛每年严格只会产一只小母牛,所有牛都不会死,而且牛长到第三年就可以帮着继续生母牛, 求n年后牛的数量
F(N) = F(N - 1) + F(N - 3)
F(N)是今年的牛
F(N - 1)是去年的牛数量 (因为所有牛都不会死,所以去年的牛会保留的今年)
F(N - 3)是三年的牛数量 (牛三年才成熟)
它最后减的数字是几就是几阶问题,这就是个3阶问题,相当于
3阶问题就有这样的解
把这个矩阵解出来,就是
// 奶牛问题
// 递归
public static int c1(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2 || n == 3) {
return n;
}
return c1(n - 1) + c1(n - 3);
}
// O(n)
public static int c2(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2 || n == 3) {
return n;
}
int cur = 3;
int pre = 2;
int ppre = 1;
int tmp = 0;
for (int i = 4; i <= n; i++) {
tmp = cur;
cur = cur + ppre;
ppre = pre;
pre = tmp;
}
return cur;
}
// O(logn)
public static int c3(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2 || n == 3) {
return n;
}
int[][] base = {
{1, 1, 0},
{0, 0, 1},
{1, 0, 0},
};
int[][] res = matrixPower(base, n - 3);
return 3 * res[0][0] + 2 * res[1][0] + 1 * res[2][0];
}
private 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[][] t = m; // 矩阵的1次方
for (; p != 0; p >>= 1) {
if ((p & 1) != 0) {
res = muliMatrix(res, t);
}
t = muliMatrix(t, t);
}
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 int s1(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return n;
}
return s1(n - 1) + s1(n - 2);
}
public static int s2(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return n;
}
int res = 2;
int pre = 1;
int tmp = 0;
for (int i = 3; i <= n; i++) {
tmp = res;
res = res + pre;
pre = tmp;
}
return res;
}
public static int s3(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];
}
题目ZeroLeftOneStringNumber
递归f(i)含义:
i长度,但是左边必然有一个1, 可以认为这个假想的1必然存在在i长度的左边, 问你i长度可以自由变换,最终整体达标多少个
比如我N = 8, 我怎么调? 我调f (7), 因为最左边必须得有1个1
如果圆圈里第一个数填0, 那么第二个数还能填0么?不能,必不合法
所以第二数只能1, 然后就让 i- 2长度任意去变
![image.png](https://cdn.nlark.com/yuque/0/2021/png/2817263/1633938840976-dcb2300c-0783-4feb-b4ca-148908a4560f.png#clientId=u8b92b561-2a7e-4&from=paste&height=196&id=u08e580fa&margin=%5Bobject%20Object%5D&name=image.png&originHeight=205&originWidth=284&originalType=binary&ratio=1&size=6896&status=done&style=none&taskId=u54f7dfd2-2ea3-49be-8d97-b8fa5d11922&width=272)
如果第1个数填的是1, 那么就让i- 1的长度任意去变
这是什么?斐波那契数列啊,这又是敏感度的问题,但这是什么尝试? 从左往右尝试的模型
// ZeroLeftOneStringNumber
// 递归
public static int getNum1(int n) {
if (n < 1) {
return 0;
}
return process(1, n);
}
private static int process(int i, int n) {
if (i == n - 1) {
return 2;
}
if (i == n) {
return 1;
}
return process(i + 2, n) + process(i + 1, n);
}
// O(n)
public static int getNum2(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2) {
return n;
}
int pre = 1;
int cur = 2;
int tmp = 0;
for (int i = 3; i <= n; i++) {
tmp = cur;
cur = pre + cur;
pre = tmp;
}
return cur;
}
// O(logn)
public static int getNum3(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];
}
题目:瓷砖
有一个2 x N的区域,然后我只给你1 x 2的瓷砖,问你用这种瓷砖把这个区域填满一共有多少种方法?
F(N): 如果你还剩一个2xN的干干净净的区域还没有填满,请你返回方法数
第一种情况: 瓷砖竖着放
第二种情况:瓷砖横着放,
所以F(N ) = F(N - 1) + F(N - 2)