假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
第一种方案===================记忆化搜索
为了提升算法效率,我们希望所有的重叠子问题都只被计算一次。为此,我们声明一个数组 mem 来记录 每个子问题的解,并在搜索过程中将重叠子问题剪枝。
public class 爬楼梯 {
int count=0;
int dfs(int i,int []memory){
if (i==1||i==2) return i;
if (memory[i]!=-1) return memory[i];
count=dfs(i-1,memory)+dfs(i-2,memory);
memory[i]=count;
return count;
}
//Plan One======记忆化搜索
int climbingStairsDFSMen(int n){
int []mem=new int[n+1];
Arrays.fill(mem,-1);
return dfs(n,mem);
}
//测试==================================================================
public static void main(String[] args) {
爬楼梯 s=new 爬楼梯();
System.out.println(s.climbingStairsDFSMen(6));
System.out.println(s.climbingStairsDP(6));//dp
System.out.println(s.climbingStairsDPComp(6));
}
}
第二种方案===================动态规划=========时间O(n)
记忆化搜索是一种“从顶至底”的方法:我们从原问题(根节点)开始,递归地将较大子问题分解为较小子问题,直至解已知的最小子问题(叶节点)。之后,通过回溯逐层收集子问题的解,构建出原问题的 解。
与之相反,动态规划是一种“从底至顶”的方法:从最小子问题的解开始,迭代地构建更大子问题的解,直至得到原问题的解。
由于动态规划不包含回溯过程,因此只需使用循环迭代实现,无须使用递归。在以下代码中,我们初始化一个数组 dp 来存储子问题的解,它起到了与记忆化搜索中数组 mem 相同的记录作用:
//Plan Two=======动态规划
int climbingStairsDP(int n){
if (n==1||n==2) return n;
int []dp=new int[n+1];
dp[1]=1;
dp[2]=2;
for (int i = 3; i <= n; i++) {
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
第三种方案=========动态规划之空间优化============空间O(1)
由于 dp[i] 只与 dp[i−1] 和 dp[i−2] 有关,因此我们无须使用一个数组 dp 来存储
所有子问题的解,而只需两个变量滚动前进即可。代码如下所示:
//Plan Two Plus======动态规划的空间优化
int climbingStairsDPComp(int n){
if (n==1||n==2)return n;
int a=1,b=2;
for (int i = 3; i <=n ; i++) {
int temp=b;
b=a+b;
a=temp;
}
return b;
}
经过记忆化处理后,所有重叠子问题都只需计算一次,时间复杂度优化至 O(n) ,这是一个
巨大的飞跃