递归与动态规划之五

本文深入探讨了递归与动态规划在解决斐波那契数列、矩阵最小路径和、换钱的最少货币数等问题上的应用。通过实例代码展示了如何运用这两种方法,包括暴力递归、记忆化搜索和动态规划的优化,特别是对最长递增子序列问题的O(NlogN)解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

斐波那契系列问题的递归与动态规划

题目

给定整数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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值