Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情。
前言
每天一道算法题,死磕算法
今天我们来讲动态规划,终于到了动态规划这一章了,这一章是最好玩的,五花八门的各种题型
什么杨辉三角,打家劫舍,爬楼梯等,让你感觉像小时候的奥赛数学题
这一章我们要多讲一些,因为动态规划的题目千变万化,大家要注意多总结
题目
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入: n = 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
思考
第一步:从题目中提取关键字
- 这道题让我们求所有的方法个数(动态规划题目一定要弄懂让求什么,不要一上来就套公式)
第二步:分析
step1:递归求解
那么我们可以得出
- 1层:1种方法
- 2层:1+1,2 2种方法
- 3层:1+1+1,2+1,1+2 3中方法
- 4层:1+1+1+1,2+1+1,1+2+1,1+1+2,2+2 5中方法 ......
所以我们可以简单得出f(n)=f(n-1)+f(n-2)的结论,就像我们小时候找规律,先从小的范围找规律,在用其他数验证
那么f(n-1)等于多少呢f(n-1)=f(n-2)+f(n-3),
那f(n-2)呢f(n-2)=f(n-3)+f(n-4)
.....
直到f(3)=f(2)+f(1),f(2)=f(2),f(1)=f(1)
也就是f(2)和f(1)是最底层了,不能在分裂了
那我们画张图表示一下这个关系吧
这不就是一颗二叉树么,想起二叉树我们想起什么,递归呀
所以我们随手就写了一个递归来结此题
var climbStairs = function(n) {
if(n === 2){
return 2;
}
if(n === 1){
return 1;
}
return climbStairs(n-1) + climbStairs(n-2);
};
但是我们这种解法放到leetcode中,发现超出时间限制,这是为什么呢?
step2:记忆化搜索法
因为我们有很多重复的解
我们看一下图中标红的地方,是不是被重复计算的地方
比如f(9) = f(8)+f(7)
我们把f(8)拆解一下,那么f(9)又等于f(7)+f(6)+f(7),是不是重复计算了两次f(7),那我们怎么办呢,我们可以把值保存起来呀,保存到数组中
所以我们又来了一版
let arr = [];
var climbStairs = function(n) {
if(n === 2){
return 2;
}
if(n === 1){
return 1;
}
if(!arr[n]){
arr[n] = climbStairs(n-1) + climbStairs(n-2);
}
return arr[n];
};
记忆搜索转化为动态规划
我们上面是一个自顶向下的过程,而动态规划恰恰相反,是一个自底向上的过程,也就是根据已知条件,迭代出来结果
const climbStairs = function(n) {
// 初始化状态数组
const f = [];
// 初始化已知值
f[1] = 1;
f[2] = 2;
// 动态更新每一层楼梯对应的结果
for(let i = 3;i <= n;i++){
f[i] = f[i-2] + f[i-1];
}
// 返回目标值
return f[n];
};
总结
总结两点什么样的题目往动态规划上想
最优子结构:你想求得的最优的答案,其实也是子结构的最优答案,这一点在接下来的找硬币中体现的非常明显,那么你就把最底层的子结构的解求出来,看他和文中给的条件之间有什么联系
重叠子问题:就是一道题目中,要计算最终结果,那么需要子问题累计起来,但是子问题会重复,就像上面标红的图片一样,会有重复计算的部分
动态规划的难点在哪里?
状态转移方程:这个不好想,有个技巧那就是动态规划一定是把每一个子结构都要求出来,所以你先从最底层子结构入手,看他们和条件有什么关系,然后大胆推想
递归转迭代:动态规划中的递归都会有重叠子问题,所以要想转化成动态规划的话,那么需要转化成迭代的方法