想着学习DP来着,看见一个例题,自己下意识就是深搜解题,所以吧两种解法都在这里贴上
爬梯子问题
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。
你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入:2
输出:2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入:3
输出:3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
解法1:DFS
代码:
#include<iostream>
using namespace std;
int c=0;
int n;
void dfs(int current){
if(current==n){
c++;
return;
}
if(current>n)
return;
//走一阶
current++;
if(current<=n)
dfs(current);
current--;
//走两阶
current+=2;
if(current<=n)
dfs(current);
current-=2;
return;
}
int main(){
ios::sync_with_stdio(false);
cin>>n;
dfs(0);
cout<<c<<endl;
return 0;
}
这是一个很简单的深搜解法,可以吧问题想象成一颗二叉树,每次面临两种选择,走一阶还是走两阶。当变量current大于N时候,说明这条路行不通,当变量current刚好等于N时,说明这是一种解法,变量c加一。
每次递归之前先判断当然的current是否已经大于或等于N,如果为真就不用进行下一步的递归操作。(剪枝操作)
如图所示,我们从最顶上的节点开始向下走,每次面临两个选择方向,一阶和二阶。
图中的黑色线路表示可行线路,蓝色表示不可行线路,最终会得到三条使得current等于N的路线,即答案为3。
此方法的效率太低,我测试了N=40的情况,跑了一分多钟才出结果,所以这个方法pass
方法2:DP
#include<iostream>
using namespace std;
int main(){
ios::sync_with_stdio(false);
int n;
cin>>n;
int p[100];
p[0]=0;p[1]=1;p[2]=2;
if(n<3){
cout<<p[n]<<endl;
return 0;
}
for(int i=3;i<=n;i++){
p[i]=p[i-1]+p[i-2];
cout<<p[n]<<endl;
return 0;
}
典型的以空间换时间的思想,吧之前到达每一阶的所有方法总和保存起来,这样到达N阶,就只与N-1阶和N-2阶有关。
因此我们可以推导出递推公式:
dp[n]=dp[n-1]+dp[n-2]
可见当计算到达第N阶的方法总数时,需要提前知道前两阶的方法总数,于是我们提前定义
dp[1]=1;
dp[2]=2;
用以计算方便
此方法效率高(N=40,秒出结果),但是牺牲了空间,作为一个强迫症患者,我的编程风格是尽量能省就省,于是改进了一下
DP+环形队列
代码如下:
#include<iostream>
using namespace std;
int main(){
ios::sync_with_stdio(false);
int n;
cin>>n;
int *p=new int[2];
p[0]=1;p[1]=2;
int pointer=0;
if(n<3){
cout<<p[n-1]<<endl;
return 0;
}
for(int i=3;i<=n;i++){
p[pointer]=p[pointer]+p[(pointer+1)%2];
pointer=(pointer+1)%2;
}
cout<<p[(pointer+1)%2]<<endl;
return 0;
}
运行结果: