动态规划(Dynamic Programming, DP)在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。 因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。 通俗一点来讲,动态规划和其它遍历算法(如深/广度优先搜索)都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存子问题的解,避免重复计算。解决动态规划问题的关键是找到状态转移方程,这样我们可以通过计算和储存子问题的解来求解最终问题。
(1)70. Climbing Stairs (Easy)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述
给定 n 节台阶,每次可以走一步或走两步,求一共有多少种方式可以走完这些台阶。
个人思路
既然的动态规划问题,那么首先需要求解的就是递推关系式。当你走到台阶上时,你可以选择下一步走一步或者两步;同样的,倒推过来就是当你走到第n节台阶时,你走上来的方法可能有两种,一种是从第n-2节走了两步上来,一种是从第n-1走了一步上来。
-
为什么不能从n-2节台阶走两次1+1过来
因为这样会与第n-1次台阶的情况重复,所计算的数目更高。
代码展示
int climbStairs(int n) {
int res;
if(n<=2)
return n;
int ans1=1;
int ans2=2;
for(int i=3;i<=n;i++){
res=ans1+ans2;
ans1=ans2;
ans2=res;
}
return res;
}
(2)198. House Robber (Easy)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述
假如你是一个劫匪,并且决定抢劫一条街上的房子,每个房子内的钱财数量各不相同。如果你抢了两栋相邻的房子,则会触发警报机关。求在不触发机关的情况下最多可以抢劫多少钱。
个人思路
这个题目和走楼梯有一点相似。但是不同的是你需要逐步判断最大值。比如说当你走到第n个房子的时候,你是否需要打劫此房子呢?这个取决于你打劫后获取的价值是否比你不打劫高。即,dp[n]=max( dp[n-1] , dp[n-2] + nums[n] )。
代码展示
int rob(vector<int>& nums) {
int n = nums.size();
if (n == 0) return 0;
if (n == 1) return nums[0];
vector<int> res(n);
res[0] = nums[0];
res[1] = max(nums[0], nums[1]);
for (int i = 2; i < n; i++) {
res[i] = max(res[i-2] + nums[i], res[i-1]);
}
return res[n-1];
}
(3)413. Arithmetic Slices (Medium)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述
给定一个数组,求这个数组中连续且等差的子数组一共有多少个。
个人思路
当输入为[1,2,3,4]时,[1,2,3]、[1,2,3,4]与[2,3,4]都为连续且等差的子数组。那么应该如何去计算等差子数组的数目且不漏掉任何一个呢?我的求解方案是确定数组的头部,并依次向后递推。 因此,我利用了一个循环,从0~n-2,依次确定后面是否有等差数组。等差数组的定义条件为nums[k]+nums[k+2]==2*nums[k+1]。
代码展示
int numberOfArithmeticSlices(vector<int>& nums) {
int res=0;
if(nums.size()<3)
return 0;
for(int i=0;i<nums.size()-2;i++){
int k=i;
while(k+2<nums.size()){
if(nums[k]+nums[k+2]==2*nums[k+1]){
res++;
}
else{
break;
}
k++;
}
}
return res;
}
题解思路
这道题略微特殊,因为要求是等差数列,可以很自然的想到子数组必定满足 num[i] - num[i-1]= num[i-1] - num[i-2]。然而由于我们对于 dp 数组的定义通常为以 i 结尾的,满足某些条件的子数组数量,而等差子数组可以在任意一个位置终结,因此此题在最后需要对 dp 数组求和。
代码展示
int numberOfArithmeticSlices(vector<int>& nums) {
int n = nums.size();
if (n < 3) return 0;
vector<int> dp(n, 0);
for (int i = 2; i < n; ++i) {
if (nums[i] - nums[i-1] == nums[i-1] - nums[i-2]) {
dp[i] = dp[i-1] + 1;
}
}
return accumulate(dp.begin(), dp.end(), 0);
}
总结
总的来说,一维数组的解答还是算比较简单的。对于动态规划来说,最重要的步骤就是找到递推公式,以及部分需要注意的边界条件。处理好这两个内容,动态规划问题就变的简单了。因此,需要认真读题,并仔细思考相关问题。
每日一题
给你一个整数 total ,表示你拥有的总钱数。同时给你两个整数 cost1 和 cost2 ,分别表示一支钢笔和一支铅笔的价格。你可以花费你部分或者全部的钱,去买任意数目的两种笔。
请你返回购买钢笔和铅笔的 不同方案数目 。
个人思路
最开始钻进了牛角尖,觉得也能用动态规划简便求解。后面越看越不对,这不就是一个简单的枚举问题么。自己被题目难度给迷惑了。
代码展示
long long waysToBuyPensPencils(int total, int cost1, int cost2) {
if(cost1<cost2)
return waysToBuyPensPencils(total, cost2,cost1);
long long cnt=0;
long long res=0;
while(cnt*cost1<=total){
res+=(total-cnt*cost1)/cost2+1;
cnt++;
}
return res;
}