一文搞懂动态规划的套路(一)
昨天和朋友聊天的时候,说到了动态规划问题,在笔试中遇到过好几次了,不过一直无从下手,最近打算就列出来动态规划问题的几个小套路。
动态规划的主要特点,就是将过去计算的结果保存起来,在下一次计算中使用曾经计算的结果,那么其核心要点只有两个了,那就是保存数据数组的设计与动态方程。
这一期主要讲一下和过去两次结果有关系的题的解法,在Leetcode中比较典型的有这几道题:
70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
746. 使用最小花费爬楼梯
数组的每个索引做为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost[i](索引从0开始)。
每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。
您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。
在都题目的时候,可以发现,题目中都指出了一个数字2,可以一次上一个台阶,也可以一次上两个台阶,每次偷盗必须至少要隔一家,那就是2户,这里的话根据循环函数编写的规则,为了不溢出,那么就需要一个哑节点,当个工具人,那么这个存放数据的数组就可以这样声明:
// 1 获取预存数据的大小
int n = cost.size();
// 2 创建一个数组
vector<int> dp(n+1, INT_MAX);
其次就是初始化了,因为dp[0]就是个工具人,那么就不需要存放数据了,放个0就好了,dp[1]就存放nums[1]
dp[0] = 0;
dp[1] = cost[0];
最后就是动态方程的编写了,不过动态方程就需要根据实际情况来编写了,那么下面就把上面三道题的程序写一下。
// 70. 爬楼梯
class Solution {
public:
int climbStairs(int n) {
// 1 定义一个容器,容量n+1,初始值为0
vector <int> f(n + 1, 0);
// 2 定义初始台阶的方法
f[0] = 1;
f[1] = 1;
// 3 开始动态规划,使用前面的结果去计算后面的结果
for (int i = 2; i <= n; i++){
f[i] = f[i-1] + f[i-2];
}
return f[n];
}
};
// 198. 打家劫舍
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.empty()) return 0;
int n = nums.size();
vector<int> dp( n + 1, 0);
dp[0] = 0;
dp[1] = nums[0];
for (int i = 2; i <= n; ++i)
dp[i] = max( dp[i-2] + nums[i-1], dp[i-1]);
return dp[n];
}
};
// 746. 使用最小花费爬楼梯
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
// 1 声明一个数字
int n = cost.size();
// 2 创建一个数组
vector<int> dp(n+1, INT_MAX);
// 3 初始化部分值
dp[0] = 0;
dp[1] = cost[0];
for (int i = 2; i <= n; i++){
dp[i] = min(dp[i-1], dp[i-2]) + cost[i-1];
}
return min(dp[n], dp[n-1]);
}
};
, 由此可见,这道题的套路基本上就出来了,只需要理清楚动态方程和输出是什么就可以了。