目录
青蛙跳台阶
问题:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 10 级的台阶总共有多少种跳法?
暴力递归
时间复杂度: 解决一个子问题的时间[f(n-1)+f(n-2)]*子问题个数[递归树结点个数]
即 O(1)*O(2^N) = O(2^N)
int recursion(int n) { if(n==1) return 1; if(n==2) return 2; return recursion(n-1)+recursion(n-2); }
优点:简单
缺点:时间复杂度高
带备忘录的递归解法(自顶向下)
备忘录:一个数组或者一个哈希map
时间复杂度:0(n)
int memoRec(int n) { if(n<=1) return 1; if(n==2) return 2; // map<int,int> m; unordered_map<int,int> m; unordered_map<int,int>::iterator it; m.insert(make_pair(0,1)); m[1]=2; m.insert(make_pair(2,2)); for(auto it=m.begin();it!=m.end();++it){ if(m.count(n)){ return it->second; }else{ m.insert(make_pair(n,(memoRec(n-1)+memoRec(n-2)))); return it->second; } } return 0; }
图解哈希表及其原理:图解哈希表及其原理 - Er_HU - 博客园 (cnblogs.com)
(unordered_map哈希表):
哈希表的创建:http://t.csdn.cn/8CE4b
动态规划(自低向上)
典型特征:最优子结构、状态转移方程、边界、重叠子问题。
在青蛙跳阶问题中:
f(n-1)和f(n-2) 称为 f(n) 的最优子结构
f(n)= f(n-1)+f(n-2)就称为状态转移方程
f(1) = 1, f(2) = 2 就是边界值
比如f(10)= f(9)+f(8),f(9) = f(8) + f(7) ,f(8)就是重叠子问题。
而这个状态迁移方程类似 斐波那契数列
动态规划的解题套路:
什么样的问题可以考虑使用动态规划解决呢?
★ 如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划。
比如一些求最值的场景,如最长递增子序列、最小编辑距离、背包问题、凑零钱问题等等,都是动态规划的经典应用场景。
动态规划的解题思路:
动态规划的核心思想就是拆分子问题,记住过往,减少重复计算。 并且动态规划一般都是自底向上的
因此到这里,基于青蛙跳阶问题,我总结了一下我做动态规划的思路:
1、穷举分析
2、确定边界
3、找出规律,确定最优子结构
4、写出状态转移方程
int dynamicPlan(int n) { if(n <= 1) return 1;//0 || 1个台阶 if(n == 2) return 2;//2 个台阶 int fn=0,fn_1=1,fn_2=2;//fn_2保存f(n-2),fn_1保存f(n-1),fn保存f(n) //f(3)=f(1)+f(2); for(int i = 3; i <= n; ++i)//i:台阶阶数 { fn = (fn_1 + fn_2) % 1000000007;//第i个台阶跳法:fn_2:i-2个台阶跳法,fn_1:i-1个台阶跳法 fn_1 = fn_2;//fn_1保存fn_2,类似f(n-1)保存f(n-2),因为f(n-1)=f(n-2)+f(n-3),相当于一层一层保存上来 fn_2 = fn;//fn_2保存fn,类似f(n-2)保存f(n),因为上一步操作中f(n-2)备份在了f(n-1)里面,现在f(n-2)就没用了,所有可以用来保存f(n),层层保存,类似临时变量 } return fn; }
动态规划优化版:http://t.csdn.cn/0NdLj
全部源码:
#include <iostream>
#include <map>
using namespace std;
/*
问题:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。
求该青蛙跳上一个 10 级的台阶总共有多少种跳法?
*/
/*
暴力递归
时间复杂度:解决一个子问题的时间[f(n-1)+f(n-2)]*子问题个数[递归树结点个数]
即 O(1)*O(2^N) = O(2^N)
*/
int recursion(int n)
{
if(n==1)
return 1;
if(n==2)
return 2;
return recursion(n-1)+recursion(n-2);
}
/*
带备忘录的递归解法(自顶向下)
备忘录:一个数组或者一个哈希map
时间复杂度:0(n)
*/
// int memoRec(int n)
// {
// }
/*
动态规划(自低向上)典型特征:最优子结构、状态转移方程、边界、重叠子问题。
在青蛙跳阶问题中:
f(n-1)和f(n-2) 称为 f(n) 的最优子结构
f(n)= f(n-1)+f(n-2)就称为状态转移方程
f(1) = 1, f(2) = 2 就是边界值
比如f(10)= f(9)+f(8),f(9) = f(8) + f(7) ,f(8)就是重叠子问题。
而这个状态迁移方程类似 斐波那契数列
动态规划的解题套路:
什么样的问题可以考虑使用动态规划解决呢?
★ 如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划。
比如一些求最值的场景,如最长递增子序列、最小编辑距离、背包问题、凑零钱问题等等,都是动态规划的经典应用场景。
动态规划的解题思路:
动态规划的核心思想就是拆分子问题,记住过往,减少重复计算。 并且动态规划一般都是自底向上的
因此到这里,基于青蛙跳阶问题,我总结了一下我做动态规划的思路:
1、穷举分析
2、确定边界
3、找出规律,确定最优子结构
4、写出状态转移方程
在斐波那契数列中:f(0) = 0, f(1)=1, f(2)=1;
fn = f(n-1)+f(n-2);
*/
int dynamicPlan(int n)
{
if(n <= 1) return 1;//0 || 1个台阶
if(n == 2) return 2;//2 个台阶
int fn=0,fn_1=1,fn_2=2;//fn_2保存f(n-2),fn_1保存f(n-1),fn保存f(n) //f(3)=f(1)+f(2);
for(int i = 3; i <= n; ++i)//i:台阶阶数
{
fn = (fn_1 + fn_2) % 1000000007;//第i个台阶跳法:fn_2:i-2个台阶跳法,fn_1:i-1个台阶跳法
fn_1 = fn_2;//fn_1保存fn_2,类似f(n-1)保存f(n-2),因为f(n-1)=f(n-2)+f(n-3),相当于一层一层保存上来
fn_2 = fn;//fn_2保存fn,类似f(n-2)保存f(n),因为上一步操作中f(n-2)备份在了f(n-1)里面,现在f(n-2)就没用了,所有可以用来保存f(n),层层保存,类似临时变量
}
return fn;
}
int main()
{
int sum1 = recursion(45);
cout<<"暴力递归结果:"<<sum1<<endl;
// int sum2 = memoRec(45);//44个台阶前
// cout<<"备忘录递归:"<<sum2<<endl;
int sum3 = dynamicPlan(45);
cout<<"动态规划之斐波拉契:"<<sum3<<endl;
}