一、 递推

1. 递推的定义与特点

● 递推思想:通过已知条件和递推关系式逐步推导出问题的解

● 适用场景:问题可以分解为相似的子问题,且子问题之间有明确的递推关系(状态转移)

● 核心特点:避免重复计算,通常时间复杂度为 O(n)或 O(n2)

2. 递推的关键要素

● 初始条件:明确递推的起点

● 递推关系式/状态转移:确定如何从已知解推导出未知解

● 求解顺序:明确计算顺序(正向或反向)

3.递推的常见应用场景

● 数列问题:斐波那契数列、爬楼梯问题等

● 路径计数:杨辉三角、网格路径问题等

● 动态规划基础:许多动态规划问题本质上是递推的延伸

二、递推应用演示/习题

1. 演示1:一维递推基础,顺推(斐波那契数列)

问题:计算斐波那契数列第n项

解法:使用一维数组存储中间结果,按关系顺序推导

#include<bits/stdc++.h>
using namespace std;
 
//设f[i]代表斐波那契数列第i项 
long long f[105]; 
 
int main(){
	int n;cin>>n;//求第n项
	//初始化状态
 
	f[1]=f[2]=1; 
	//从第3项开始推导至第n项 
	//递推关系/状态转移:当前项 = 前一项+前二项
	for(int i=3;i<=n;i++){
		f[i] = f[i-1] + f[i-2];
	} 
	//输出第n项 
	cout<<f[n]; 
	return 0;
}
 
 

输入样例1: 6 输出样例1: 8

拓展:其他类似于爬楼梯,纸张折痕等,都可以参考这种思路,把问题求解过程变成求数列第n项的问题

台阶问题:一次走1步或者2步,上n级台阶方案数量?

//初始化状态
f[1] = 1; f[2]=2
//从第三项开始递推
f[i]=f[i-1] + f[i-2]

将一张长方形的纸对折,可得到一条折痕,继续对折,对折时每次折痕与上次的折痕保持平行,连续对折三次后,可得到 7 条折痕,那么对折 n 次,可得到几条折痕?

//从第一项开始递推
f[i] = f[i-1] * 2 + 1
 
//当然这一题还有O(1)的做法
2^n - 1

2. 演示2,一维递推变式,反推(猴子吃桃问题)

问题:一只小猴买了若干个桃子。第一天他刚好吃了这些桃子的一半,又贪嘴多吃了一个;接下来的每一天它都会吃剩余的桃子的一半外加一个。第 n 天早上起来一看,只剩下 1 个桃子了。请问小猴买了几个桃子? 。

解法:第n天剩1个桃子,求第一天剩几个,本质上也是求数列的第n项,只是用最后一天剩下的1个桃子来作为初始状态

#include<bits/stdc++.h>
using namespace std;
 
 
//设f[i]代表猴子倒数第i天的桃子数量
//第1天的桃子数量就是数列第n项 
long long f[105]; 
 
int main(){
	int n;cin>>n;//求第n项
	//初始化状态
	f[1]=1; 
	
	//从第2项开始推导至第n项 
	//递推关系/状态转移:当前项 = 前一项+前二项
	for(int i=2;i<=n;i++){
		f[i] = (f[i-1]+1) * 2;
	} 
	//输出第n项 
	cout<<f[n]; 
	
	return 0;
}

输入样例1: 4 输出样例1: 22

3. 演示3:杨辉三角

问题:根据规律,输出n行的杨辉三角

解法:每行的第一列最后一列均为1,其他位置均为 “上一行 + 上一行前一列”

#include<bits/stdc++.h>
using namespace std;
 
//设f[i][j] 代表杨辉三角i行j列的位置
int f[25][25];
 
int main() {
	int n;
	cin >> n; //输出n行的杨辉三角
 
	//为方便大家观察和理解递推思想,我们把1的位置单独拎出来初始化
	//tips:实际上这一步可以直接结合在递推中进行 
	for (int i = 1; i <= n; i++) {
		f[i][1] = 1; //所有第一列都是1 
		f[i][i] = 1; //每行的最后一列是1 
	}
	
	//递推 : 除去已经初始化完成的位置 
	//其他位置: 当前项 = 上一行 + 上一行前一列 
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= i; j++) {
			//不是第一列或者最后一列才需要遵循推导归路 
			if (j != 1 && j != i) {
				f[i][j] = f[i - 1][j - 1] + f[i - 1][j];
			}
			cout << f[i][j] << " "; //推导完直接输出 
		}
		cout << endl;//记得换行 
	}
	return 0;
}

 

4. 演示4:路径计数,二维递推(网格路径问题)

问题:一个N×N的网格,你一开始在(1,1),即左上角。每次只能移动到下方相邻的格子或者右方相邻的格子,问到达(N,N),即右下角有多少种方法。

 

解法

● 这里以4*4的网格为例,根据只能移向右和向下的移动规则,则移动到第一行第一列的位置方案只能是1,因为只能从左边或上方的格子移动过来

1

1

1

1

1

 

 

 

1

 

 

 

1

 

 

 

● 初始化完成后,我们可以开始推导剩下的格子路线有什么规律,以[2,2]的格子为例子,根据移动规则,这个格子只能从上方[1,2]和左方[2,1]移动到达,也意味着到达上述两地点的路线,都可以到达[2,2],那么路线数量[2,2] = [1,2] + [2,1]

1

1

1

1

1

2

 

 

1

 

 

 

1

 

 

 

 

● 根据上述规律,我们可以总结出递推规律:[i,j] = [i-1,j] + [i,j-1],那么以此类推剩下的单元格,最后右下角的位置就代表了左上角起点[1,1]到达[n,n]的路线数量

1

1

1

1

1

2

3

4

1

3

6

10

1

4

10

20

#include<bits/stdc++.h>
using namespace std;
 
//设f[i][j] 代表[1,1]到达[i,j]的路线数量
int f[25][25];
 
int main() {
	int n;
	cin >> n; //n*n地图
 
	//初始化状态
	for (int i = 1; i <= n; i++) f[1][i] = 1; //第一行 
	for (int i = 1; i <= n; i++) f[i][1] = 1; //第一列
	
	//递推:从[2,2]开始递推
	//递推规律/状态转移:当前位置 = 上方 + 左方 
	for(int i=2;i<=n;i++){
		for(int j=2;j<=n;j++){
			f[i][j] = f[i-1][j] + f[i][j-1];
		}
	} 
	
	//输出到达[n,n]的路线数量
	cout<<f[n][n];
	 
	return 0;
}

输入样例1: 4 输出样例1: 20

 

5 演示4拓展:路径计数带障碍物

问题:一个N×N的网格,你一开始在(1,1),即左上角。每次只能移动到下方相邻的格子或者右方相邻的格子,问到达(N,N),即右下角有多少种方法。但是这个问题太简单了,所以现在有M个格子上有障碍,即不能走到这M个格子上。(数据保证起始点和终止点无障碍物,并且起点到终点至少存在一条通路)。

解法

● 虽然增加了障碍物,但思路依然可以延续上述的方式,我们可以增加一个对应地图大小的vis标记数组来标记[i,j]位置是否可以通行,不能通行的地方路线数量为0,不需要推导。例如[3,3]是障碍物,那我们推导时跳过这个位置就行。

 

1

1

1

1

1

2

3

4

1

3

0

4

1

4

4

8

● 额外需要注意的一点,障碍物应在初始化数组前标记,因为初始化过程中碰到障碍物的话,应停止初始化,因为后续位置也无法到达,不注意这一点可能会导致递推结果出错。

1

1

1

1

1

 

 

 

0

 

 

 

0

 

 

 

 

#include<bits/stdc++.h>
using namespace std;
 
//设f[i][j] 代表[1,1]到达[i,j]的路线数量
int f[25][25];
//vis[i][j]为1代表[i,j]不能通行,为0代表可以通行
bool vis[25][25];
 
int main() {
	int n,m;
	cin >> n >> m; //n*n地图 ,m个障碍物 
	
	//标记障碍物 
	for(int i=1;i<=m;i++){
		int x,y;cin>>x>>y; // 障碍物坐标
		vis[x][y] = 1; 
	}
	
	//初始化状态
	for (int i = 1; i <= n; i++) {
		if (vis[1][i] == 1) break;
		f[1][i] = 1; //第一行
	}
	for (int i = 1; i <= n; i++) {
		if (vis[i][1] == 1) break;
		f[i][1] = 1; //第一列
	}
	
	//递推:从[2,2]开始递推
	//递推规律/状态转移:当前位置 = 上方 + 左方
	for (int i = 2; i <= n; i++) {
		for (int j = 2; j <= n; j++) {
			if (vis[i][j] == 0) {
				f[i][j] = f[i - 1][j] + f[i][j - 1];
			}
		}
	}
 
	//输出到达[n,n]的路线数量
	cout << f[n][n];
 
	return 0;
}
 

输入样例1:3 1 输出样例1:5

3 1

● 练习过程中若地图比较大,可能会导致递推结果溢出int类型,所以记得开long long或者根据题目取模噢