斐波那契系列问题的递归与动态规划
题目
给定整数N,返回斐波那契数列的第N项。
补充题目1:给定整数N,代表台阶数,一次可跨越1个或2个台阶,返回有多少种走法。
补充题目2:假设农场成熟母牛每年生一只小母牛,永远不会死。第一年农场有1只成熟母牛,从第二年开始,母牛开始生小母牛。每只小母牛3年之后成熟可以再生小母牛。给定整数N,求N年后牛的数量。
代码
补充题目2的log(N)解法类似于补充题目一,不再进行测试,具体代码可参考书籍。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int fibonacciRecur(int n)
{
if (n <= 2)
return 1;
return fibonacciRecur(n - 1) + fibonacciRecur(n - 2);
}
int fibonacciIter(int n)
{
int first = 1;
int second = 1;
int res = 0;
for (int i = 2; i < n; i++)
{
res = first + second;
first = second;
second = res;
}
return res;
}
//logN解法
vector<vector<int>> multiMatrix(vector<vector<int>> m1, vector<vector<int>> m2)
{
int row = m1.size();
int col = m2[0].size();
int midVal = m1[0].size();
vector<vector<int>> res(row, vector<int>(col, 0));
for (int i = 0; i < row; i++)
{
for (int k = 0; k < midVal; k++)
{
int r = m1[i][k];
for (int j = 0; j < col; j++)
res[i][j] += r * m2[k][j];
}
}
return res;
}
vector<vector<int>> matrixPower(vector<vector<int>> m, int p)
{
int row = m.size();
int col = m[0].size();
vector<vector<int>> res(row, vector<int>(col, 0));
for (int i = 0; i < row; i++)
res[i][i] = 1;
vector<vector<int>> tmp(m);
for (; p != 0; p >>= 1)
{
if ((p & 1) != 0)
res = multiMatrix(res, tmp);
tmp = multiMatrix(tmp, tmp);
}
return res;
}
int fiboncci3(int n)
{
if (n < 1)
return 0;
if (n == 1 || n == 2)
return 1;
vector<vector<int>> base(2, vector<int>(2, 0));
base[0][0] = 1;
base[0][1] = 1;
base[1][0] = 1;
base[1][1] = 0;
vector<vector<int>> res = matrixPower(base, n - 2);
return res[0][0] + res[1][0];
}
//当给定台阶数,一次可以走2个或1个台阶时,总共走法
//同样可以根据斐波那契数列写出对应的循环或递归程序,只是初始项不同
int fiboncci2Recur(int n)
{
if (n <= 2)
return n;
return fiboncci2Recur(n - 1) + fiboncci2Recur(n - 2);
}
int fiboncci2Iter(int n)
{
if (n <= 2)
return n;
int first = 1;
int second = 2;
int res = 0;
for (int i = 2; i < n; i++)
{
res = first + second;
first = second;
second = res;
}
return res;
}
int fiboncci32(int n) //对应的初始化矩阵是相同的,只是初始时系数不同
{
if (n < 1)
return 0;
if (n == 1 || n == 2)
return n;
vector<vector<int>> base(2, vector<int>(2, 0));
base[0][0] = 1;
base[0][1] = 1;
base[1][0] = 1;
base[1][1] = 0;
vector<vector<int>> res = matrixPower(base, n - 2);
return 2 * res[0][0] + res[1][0];
}
/*同理,预估牛的个数类似于斐波那契数列,c(n) = c(n-1) + c(n-3),
根据递推公式可以得到三阶的矩阵方程;如果递归公式严格符合
f(n) = a*f(n-1) + b*f(n-2) + ... + i*f(n-i), 那么可以用i阶方阵
加速计算过程*/
int main()
{
int N = 9;
int f1 = fibonacciRecur(N);
int f2 = fibonacciIter(N);
int f3 = fiboncci3(N);
cout << f1 << " " << f2 << " " << f3 << endl;
int f11 = fiboncci2Recur(N);
int f21 = fiboncci2Iter(N);
int f31 = fiboncci32(N);
cout << f11 << " " << f21 << " " << f31 << endl;
getchar();
return 0;
}
矩阵的最小路径和
题目
给定一个矩阵m,从左上角开始每次只能向右或向下走,最后到右下角,路径上所有数字累加即为路径和,返回所有路径中最小的路径和。
代码
详细解答过程请参考原书
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int minSum1(vector<vector<int>> in)
{
int row = in.size();
int col = in[0].size();
vector<vector<int>> res(row, vector<int>(col, 0));
res[0][0] = in[0][0];
for (int i = 1; i < col; i++)
{
res[0][i] = res[0][i - 1] + in[0][i];
}
for (int j = 1; j < row; j++)
res[j][0] = res[j - 1][0] + in[j][0];
for (int i = 1; i < row; i++)
{
for (int j = 1; j < col; j++)
res[i][j] = min(res[i - 1][j], res[i][j - 1]) + in[i][j];
}
return res[row - 1][col - 1];
} //时间复杂度为O(m+n),空间复杂度也是一样,当经过空间压缩,
//可以是空间复杂度达到O(min(m, n)),如下面方法所示
/*空间压缩*/
int minSumPath2(vector<vector<int>> in)
{
if (in.size() < 1 || in[0].size() < 1)
return 0;
int more = max(in.size(), in[0].size());
int less = min(in.size(), in[0].size());
bool rowMore = more == in.size();
vector<int> res(less, 0);
res[0] = in[0][0];
for (int i = 1; i < less; i++)
{
res[i] = res[i - 1] + (rowMore ? in[0][i] : in[i][0]);
}
for (int i = 1; i < more; i++)
{
res[0] = res[0] + (rowMore ? in[i][0] : in[0][i]);
for (int j = 1; j < less; j++)
res[j] = min(res[j - 1], res[j]) + (rowMore ? in[i][j] : in[j][i]);
//注意此处i,j的交换
}
return res[less - 1];
}
int main()
{
int m, n;
cin >> m >> n;
vector<vector<int>> in(m, vector<int>(n, 0));
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
cin >> in[i][j];
}
}
int minSum = minSum1(in);
cout << minSum << endl;
int minSum21 = minSumPath2(in);
cout << minSum21 << endl;
getchar();
return 0;
}
换钱的最少货币数
题目
给定数组arr,arr中所有的值都为正数且不重复,每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求组成aim的最少货币数
代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define MAX INT_MAX
int minCoins1(vector<int> in, int aim)
{
if (in.size() < 1 || aim < 0)
return 0;
int m = in.size();
// int MAX = INT_MAX;
vector<vector<int>> dp(m, vector<int>(aim + 1, 0));
for (int j = 1; j <= aim; j++)
{
dp[0][j] = MAX;
if (j - in[0] >= 0 && dp[0][j - in[0]] != MAX)
dp[0][j] = dp[0][j - in[0]] + 1;
}
int left = 0;
for (int i = 1; i < m; i++)
{
for (int j = 1; j <= aim; j++)
{
left = MAX;
if (j - in[i] >= 0 && dp[i][j - in[i]] != MAX)
left = dp[i][j - in[i]] + 1;
dp[i][j] = min(left, dp[i - 1][j]);
}
}
return dp[m - 1][aim] != MAX ? dp[m - 1][aim] : -1;
}
//额外的方法进行空间压缩, 压缩后空间复杂度达到O(aim)
int minCoins2(vector<int> in, int aim)
{
if (in.size() < 1 || aim < 1)
return -1;
int m = in.size();
vector<int> dp(aim + 1, 0);
for (int j = 1; j <= aim; j++)
{
dp[j] = MAX;
if (j - in[0] >= 0 && dp[j - in[0]] != MAX)
dp[j] = dp[j - in[0]] + 1;
}
int left = 0;
for (int i = 1; i < m; i++)
{
for (int j = 1; j <= aim; j++)
{
left = MAX;
if (j - in[i] >= 0 && dp[j - in[i]] != MAX)
left = dp[j - in[i]] + 1;
dp[j] = min(left, dp[j]);
}
}
return dp[aim] != MAX ? dp[aim] : -1;
}
/*每种类型的change只有一张的情况下*/
/*dp[i - 1][j]表示可以任意使用in[0...i-1]的情况下,组成j所需最小张数
in[i]只有一张,考虑dp[i][j]值可能等于dp[i-1][j-arr[i]]+1;
如果j-in[i] < 0,说明in[i]过大,令dp[i][j] = dp[i-1][j]即可*/
int minCoins3(vector<int> in, int aim)
{
if (in.size() < 1 || aim < 1)
return -1;
int m = in.size();
vector<vector<int>> dp(m, vector<int>(aim + 1, 0));
for (int j = 1; j <= aim; j++)
dp[0][j] = MAX;
if (in[0] <= aim)
dp[0][in[0]] = 1;
int leftup = 0;
for (int i = 1; i < m; i++)
{
for (int j = 1; j <= aim; j++)
{
leftup = MAX;
if (j - in[i] >= 0 && dp[i - 1][j - in[i]] != MAX)
leftup = dp[i - 1][j - in[i]] + 1;
dp[i][j] = min(dp[i - 1][j], leftup);
}
}
return dp[m - 1][aim] != MAX ? dp[m - 1][aim] : -1;
}
//路径压缩
int minCoins4(vector<int> in, int aim)
{
if (in.size() < 1 || aim < 1)
return -1;
int m = in.size();
vector<int> res(aim + 1, 0);
for (int i = 1; i <= aim; i++)
{
res[i] = MAX;
}
if (in[0] <= aim)
res[in[0]] = 1;
int leftup = 0;
for (int i = 1; i < m; i++)
{
for (int j = aim; j > 0; j--)
{
leftup = MAX;
if (j - in[i] >= 0 && res[j - in[i]] != MAX)
leftup = res[j - in[i]] + 1;
res[j] = min(leftup, res[j]);
}
}
return res[aim] != MAX ? res[aim] : -1;
}
int main()
{
int len, aim;
cin >> len >> aim;
vector<int> in(len, 0);
for (int i = 0; i < len; i++)
cin >> in[i];
int res11 = minCoins1(in, aim);
int res12 = minCoins2(in, aim);
cout << res11 << " " << res12 << endl;
int res21 = minCoins3(in, aim);
int res22 = minCoins4(in, aim);
cout << res21 << " " << res22 << endl;
getchar();
return 0;
}
换钱的方法数
题目
给定数组arr,arr中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求换钱有多少种方法。
代码
代码中根据书中所示,依次给出了暴力递归,记忆化搜索和动态规划三种解的情况,最后还包含了空间压缩减少空间复杂度。具体分析请参考原书
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
/*递归的方法*/
int process1(vector<int> in, int index, int aim)
{
int res = 0;
if (index == in.size())
res = aim == 0 ? 1 : 0;
else
{
for (int i = 0; i * in[index] <= aim; i++)
res += process1(in, index + 1, aim - in[index] * i);
}
return res;
}//存在大量的重复计算
int coins1(vector<int> in, int aim)
{
if (in.size() < 1 || aim < 1)
return 0;
return process1(in, 0, aim);
}
/*记忆化搜索记录之前每次计算过的值,每次计算之前进行查询,如果计算过
则不再进行重复计算,否则再次进行计算*/
int process2(vector<int> in, int index, int aim, vector<vector<int>> &map)
{
int res = 0;
if (index == in.size())
res = aim == 0 ? 1 : 0;
else
{
int mapValue = 0;
for (int i = 0; i * in[index] <= aim; i++)
{
mapValue = map[index + 1][aim - in[index] * i];
if (mapValue != 0)
res += mapValue == -1 ? 0 : mapValue;
else
res += process2(in, index + 1, aim - in[index] * i, map);
}
}
map[index][aim] = res == 0 ? -1 : res;
return res;
}
int coins2(vector<int> in, int aim)
{
if (in.size() < 1 || aim < 0)
return 0;
vector<vector<int>> mp(in.size() + 1, vector<int>(aim + 1, 0));
return process2(in, 0, aim, mp);
}
/*动态规划的方法,dp[i][j]表示使用arr[0..i]货币的情况下,组成钱数j的方法总数*/
int coins3(vector<int> in, int aim)
{
if (in.size() < 1 || aim < 0)
return 0;
int len = in.size();
vector<vector<int>> dp(len, vector<int>(aim + 1, 0));
for (int i = 0; i < len; i++)
{
dp[i][0] = 1;
}
for (int j = 1; in[0] * j <= aim; j++)
dp[0][in[0] * j] = 1;
int num = 0;
for (int i = 1; i < len; i++)
{
for (int j = 1; j <= aim; j++)
{
num = 0;
for (int k = 0; j - in[i] * k >= 0; k++)
num += dp[i - 1][j - in[i] * k];
dp[i][j] = num;
}
}
return dp[len - 1][aim];
}
int coins4(vector<int> in, int aim)
{
if (in.size() < 1 || aim < 0)
return 0;
int len = in.size();
vector<vector<int>> dp(len, vector<int>(aim + 1, 0));
for (int i = 0; i < len; i++)
dp[i][0] = 1;
for (int j = 1; in[0] * j <= aim; j++)
{
dp[0][in[0] * j] = 1;
}
for (int i = 1; i < len; i++)
{
for (int j = 1; j <= aim; j++)
{
dp[i][j] = dp[i - 1][j];
dp[i][j] += j - in[i] >= 0 ? dp[i][j - in[i]] : 0;
}
}
return dp[len - 1][aim];
}
/*结合空间压缩*/
int coins5(vector<int> in, int aim)
{
if (in.size() < 1 || aim < 0)
return 0;
int len = in.size();
vector<int> dp(aim + 1, 0);
for (int i = 0; in[0] * i <= aim; i++)
dp[i * in[0]] = 1;
for (int i = 1; i < len; i++)
{
for (int j = 1; j <= aim; j++)
dp[j] += j - in[i] >= 0 ? dp[j - in[i]] : 0;
}
return dp[aim];
}
int main()
{
int n, aim;
cin >> n >> aim;
vector<int> input(n, 0);
for (int i = 0; i < n; i++)
cin >> input[i];
int res1 = coins1(input, aim);
int res2 = coins2(input, aim);
int res3 = coins3(input, aim);
int res4 = coins4(input, aim);
int res5 = coins5(input, aim);
cout << res1 << " " << res2 << " " << res3 << " " << res4 << " " << res5 << endl;
getchar();
return 0;
}
最长递增子序列
题目
给定数组arr,返回arr的最长递增子序列。
如果arr长度为N,请实现时间复杂度为*O(NlogN)*的方法。
代码
给出O(N2)和O(NlogN)两种解法,O(NlogN)的解法是在求dp数组的过程中用二分查找的方法进行了优化。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
vector<int> getdp1(vector<int> in)
{
int len = in.size();
vector<int> dp(len, 0);
for (int i = 0; i < len; i++)
{
dp[i] = 1;
for (int j = 0; j < i; j++)
{
if (in[i] > in[j])
dp[i] = max(dp[i], dp[j] + 1);
}
}
return dp;
}
vector<int> generateLIS(vector<int> in, vector<int> dp)
{
int len = 0;
int index = 0;
int dLen = dp.size();
for (int i = 0; i < dLen; i++)
{
if (dp[i] > len)
{
len = dp[i];
index = i;
}
}
vector<int> lis(len, 0);
lis[--len] = in[index];
for (int i = index; i >= 0; i--)
{
if (in[i] < in[index] && dp[i] == dp[index] - 1)
{
lis[--len] = in[i];
index = i;
}
}
return lis;
}
vector<int> lis1(vector<int> in)
{
vector<int> lis;
if (in.size() < 1)
return lis;
vector<int>dp = getdp1(in);
lis = generateLIS(in, dp);
return lis;
}
/*将得到dp数组的过程使用二分查找的方法优化到O(nlogn)
在dp数组中查找第一个大于arr[i]的过程*/
vector<int> getdp2(vector<int> in)
{
int len = in.size();
vector<int> dp(len, 0);
vector<int> ends(len, 0);
ends[0] = in[0];
dp[0] = 1;
int right = 0;
int l = 0;
int r = 0;
int m = 0;
for (int i = 1; i < len; i++)
{
l = 0;
r = right;
while (l <= r)
{
m = (l + r) / 2;
if (in[i] > ends[m])
l = m + 1;
else
r = m - 1;
}
right = max(right, l);
ends[l] = in[i];
dp[i] = l + 1;
}
return dp;
}
vector<int> lis2(vector<int> in)
{
vector<int> res;
if (in.size() < 1)
return res;
vector<int> dp = getdp2(in);
res = generateLIS(in, dp);
return res;
}
int main()
{
int len;
cin >> len;
vector<int> in(len, 0);
for (int i = 0; i < len; i++)
cin >> in[i];
vector<int> res1 = lis1(in);
vector<int> res2 = lis2(in);
cout << "lis1 :";
for (int i = 0; i < res1.size(); i++)
cout << res1[i] << " ";
cout << endl;
cout << "lis2 :";
for (int i = 0; i < res2.size(); i++)
cout << res2[i] << " ";
cout << endl;
getchar();
return 0;
}