日期:2019.5.17 星期五 多云
今天只看了一些算法题,C++还有深度学习都没有看,该打。。。。没完成任务
斐波那契数列题目及扩展
对于一些问题类似于斐波那契数列的,可以写成
f(n) = f(n-1) + f(n-2)
之类的递推式,是可以有三种解题思路的。
- 最简单的就是直接套公式,时间复杂度都是O(2^N)
- 第二种就是循环遍历,(比如f(1) = 1,f(2) = 1,那么令a = 1,b = 1,tmp = a + b; 从第3个数开始向后计算,f(3) = a + b,然后a = b,b = f(3);f(4) = a + b;一直到f(n);这种方法的时间复杂度是O(N)
- 最绝妙的是时间复杂度为O(logN),看到了logN,那么一定有关二进制,二分法之类的。这里就要用到矩阵快速幂。
问题1
求一个数的n次幂
如果是循环n次,那么时间复杂度太高,这时候就要用到快速幂算法。n看成一个二进制数,那么位数是1的位,进行相乘。比如说n = 5,那么二进制数是101,只需要进行1*m(20) * 0*m(21) * 1*m(22).只需要进行3次运算,不需要进行5次运算。
int kuaisumi(int m,int n) {
long long res = 1;
while (n) {
if (n & 1) res *= m;
m *= m;
n = n >> 1;
}
return res;
}
问题2
斐波那契数列
f(1) = 1,f(2) = 1,f(n) = f(n-1) + f(n-2) (n >= 3)
方法1:递归,时间复杂度O(2^N)
int fib1(int n) {
if (n == 1 || n == 2)
return 1;
return fib1(n-1) + fib1(n-2);
}
方法2:方法1太多重复的计算,所以改进一下,时间复杂度为O(N)
int fib2(int n) {
if (n == 1 || n == 2)
return 1;
int a = 1,b = 1,res;
for (int i = 3; i <= n; i++) {
res = a + b;
a = b;
b = res;
}
return res;
}
方法3:快速矩阵幂。时间复杂度是O(logN),看到log那么就要用到二分法或者二进制相关算法。斐波那契数列是一个二维递推数列。
//矩阵乘法
vector<vector<int>> matrixMul(vector<vector<int>>& m1, vector<vector<int>>& m2) {
int n1 = m1.size(),n2 = m1[0].size(),n3 = m2.size();
vector<vector<int>> res(n1,vector<int>(n3));
for (int i = 0; i < n1; i++) {
for (int j = 0; j < n3; j++) {
for (int k = 0; k < n2; k++) {
res[i][j] += m1[i][k] * m2[k][j];
}
}
}
return res;
}
//快速矩阵幂
vector<vector<int>> matrixPower(vector<vector<int>>& m,int n) {
vector<vector<int>> res(m.size(),vector<int>(m.size()));
for (int i = 0; i < m.size(); i++) res[i][i] = 1;
while (n) {
if (n & 1) res = matrixMul(res,m);
m = matrixMul(m,m);
n = n >> 1;
}
return res;
}
int fib3(int n) {
if (n == 1 || n == 2)
return 1;
vector<vector<int>> m = {{1,1},{1,0}};
vector<vector<int>> tmp = matrixPower(m,n-2);
return tmp[0][0] + tmp[1][0];
}
问题2
青蛙跳台阶,每次只能跳一阶或者两阶,当台阶数为n的时候,有多少种方式?
f(n) = f(n-1) + f(n-2),f(1) = 1,f(2) = 2;
这道题和斐波那契数列一样。
方法1:时间复杂度O(2^N)
int tj1(int n) {
if (n == 1 || n == 2)
return n;
return tj1(n-1) + tj1(n-2);
}
方法2:时间复杂度O(N)
int tj2(int n) {
if (n == 1 || n == 2)
return n;
int a = 1,b = 2,res;
for (int i = 3; i <= n; i++) {
res = a + b;
a = b;
b = res;
}
return res;
}
方法3:时间复杂度O(log N)
int tj3(int n) {
if (n == 1 || n == 2)
return n;
vector<vector<int>> m = {{1,1},{1,0}};
vector<vector<int>> tmp = matrixPower(m,n-2);
return 2 * tmp[0][0] + tmp[1][0];
}
问题3
成熟母牛每年都可以生一只小牛,小牛3年后才可以成为成熟母牛。农场里第一年有一只成熟母牛,计算出n年后农场有多少牛?
这里很明显是有一个递推公式,分析一下,第n年母牛数量来自于n-1年的数量和新出生的数量(n-3年)之和。也就是
f(n) = f(n-1) + f(n-3) f(1)=1,f(2)=2,f(3)=3;
方法1:时间复杂度O(2^N)
int fib1(int n) {
if (n == 1 || n == 2 || n == 3)
return n;
return fib1(n-1) + fib1(n-3);
}
方法2:时间复杂度O(N)
int fib2(int n) {
if (n == 1 || n == 2 || n == 3)
return n;
int a = 1,b = 2,c = 3,res;
for (int i = 4; i <= n; i++) {
res = a + c;
a = b;
b = c;
c = res;
}
return res;
}
方法3:时间复杂度O(logN),最后递推式在矩阵中怎么计算需要注意,很容易错。
int fib3(int n) {
if (n == 1 || n == 2 || n == 3)
return n;
vector<vector<int>> m = {{1,1,0},{0,0,1},{1,0,0}};
vector<vector<int>> tmp = matrixPower(m,n-3);
return 3 * tmp[0][0] + 2 * tmp[1][0] + tmp[2][0]; // 注意这里,怎么得到的这个公式,很容易出错,为什么要2 * tmp[1][0]。
}
矩阵的最小路径和
一个矩阵从左上角开始,只能向右和向下前进,问题到达右下角路径上和最小的那条路径和。
方法1:用动态规划,dp[i][j]表示位置(i,j)最短路径和。那么就会有转移方程如下,需要新建一个M*N的数组,时间复杂度为O(M*N),空间复杂度也是O(M * N)。
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + nums[i][j]
dp[0][j] += dp[0][j-1];
dp[i][0] += dp[i-1][0]
方法2: 不需要新建数组,在原数组上进行修改。这时候空间复杂度就为O(1),时间复杂度为O(M*N)
int minDis(vector<vector<int>>& nums) {
int m = nums.size(),n = nums[0].size();
for (int i = 1; i < m; i++) nums[i][0] = nums[i-1][0] + nums[i][0];
for (int j = 1; j < n; j++) nums[0][j] = nums[0][j-1] + nums[0][j];
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
nums[i][j] = min(nums[i-1][j],nums[i][j-1]) + nums[i][j];
}
}
return nums[m-1][n-1];
}
方法3: 还是动态规划,但是可以把空间复杂度变为O(min(M,N)).
int minDis2(vector<vector<int>>& nums) {
int m = nums.size(),n = nums[0].size();
int more = max(m,n),less = min(m,n);
vector<int> dp(less,0);
dp[0] = nums[0][0];
bool flag = (less == n);
for (int i = 1; i < less;i++) dp[i] = (flag ? nums[0][i] : nums[i][0]) + dp[i-1];
for(int i = 1; i < more; i++) {
dp[0] += flag ? nums[i][0] : nums[0][i];
for (int j = 1; j < less; j++) {
dp[j] = min(dp[j-1],dp[j]) + (flag ? nums[i][j] : nums[j][i]);
}
}
return dp[less-1];
}