前言
在这篇文章中,荔枝会接着前面的分治思想来继续学习动态规划的相关知识,同时对动态规划、分治思想和贪心算法这三个概念进行区分。本篇文章依旧是荔枝作为一个算法小白的学习笔记,在后期更深入地学习后荔枝可能会对这些基础算法中的典型题目和解题思路再进行总结吧哈哈哈。
文章目录
一、动态规划——DP
1.1 基础概念
动态规划不同于递归的特点在于其对于子问题的求解不具有重复性,相较于带有备忘录的递归解法(自顶向下),动态规划是自底向上的。动态规划常常适用于解决具有重叠子问题和最优子结构性质的问题,也就是说使用动态规划时我们需要能够穷举出所有可能的答案并发现存在重叠子问题。
最优子结构:一个问题的解包含其子问题的最优解,则该问题就具有最优子结构的性质,我们可以利用最优子结构的接来推导出问题的解。最优子结构可以作为判断是否用动态规划的一个较好的依据。
重叠子问题:再使用递归算法来求解斐波那契数列的问题时,我们常常会发现递归调用函数重复求解子问题,导致了时间上的资源浪费,这时候会采取动态规划来储存求解过的子问题。
1.2 使用场景
动态规划常用于一些求最值的场景,如最长递增子序列、最小编辑距离、背包问题、凑零钱问题等等,都是动态规划的经典应用场景。
1.3 常见解题步骤
- 穷举分析:穷举出所有可能的答案类型;
- 确定边界条件;
- 找到最优子结构:最优子结构就是保证转移到n的状态方程是最优的并且与后面的决策没有关系,可以放心的使用局部最优解。
- 确定状态转移方程并用代码实现
1.4 样题示例
LeetCode 70.爬楼梯
题目:
假设你正在爬楼梯。需要
n
阶你才能到达楼顶。每次你可以爬
1
或2
个台阶。你有多少种不同的方法可以爬到楼顶呢?输入样例:
n=3
输出样例:
3
解释:
1. 1+1+1
2. 1+2
3. 2+1
对于爬楼梯问题我们在求解到达第n级台阶的方法时,我们可以往前看一步,到达第n阶台阶的方法就是从第n-1级台阶上来或者是从n-2级台阶上来,因此到达第n阶台阶的方法就是到达第n-1阶台阶的方法加上到达第n-2阶级台阶的方法之和。
核心代码:
class Solution {
public:
int climbStairs(int n) {
int r1=1,result=1,r2;
if(n==0) return r1;
if(n==1) return result;
for(int i=2;i<=n;i++){
r2=r1;
r1=result;
result = result+r2;
}
return result;
}
};
从上面的题目分析中我们可以看出爬楼梯问题其实就是斐波那契数列的另一种问法,上面的代码采用了滚动数组的方法降低了动态规划的时间复杂度。通过自底向上递推过程使用三个变量分别贮存数据并将得到的新数据覆盖原有的数据,最终得到题解。
二、贪心算法
2.1 基础概念
贪心算法,顾名思义就是计算机模拟一个贪心的人进行决策,在贪婪的作用下,计算机变得十分短视,它只会对每一步操作之前按某种指标选取最优处理的操作,即做到一个局部最优解。但是局部最优并不是全局最优,因此使用贪心算法需要确保正确性。
2.2 适用范围
贪心算法在最优子结构的问题中尤为有效。也就是说在最优子结构问题中,每个子问题的最优解就是最终问题的最优解。
贪心算法与动态规划的区别
贪心算法对于每个子问题都会做出最优选择并不能回退,而动态规划则是可以记住之前的状态并根据该状态来对当前的问题做出选择,可以回退。也就是说:动态规划是自底向上的全局最优解一定包含某个局部最优解,但不一定包含前一个局部最优解;而贪心算法则是自顶向下的,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题,每一步的最优解一定包含上一步的最优解,且不保留上一步的最优解。
贪心算法与动态规划的联系
贪心算法和动态规划都采用了分而治之的思想,将一个大问题分解成一个个小的问题来解决,同时都需要拥有最优子结构,所有的贪心问题都可以用动态规划来求解,贪心算法是动态规划的特例。动态规划和分治算法也有不同:分治算法要求不能存在重复子问题、但动态规划的高效性就体现在针对回溯算法产生的大量重复子问题的解决能力上。
2.3 样题示例
LeetCode 135.分发糖果
题目:
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的最少糖果数目 。输入示例:
ratings = [1,0,2]
输出示例:
5
对于第i个小孩,如果它的权重比相邻的小孩高,那么他分到的糖果数目需要在相邻位置达到最多,这就是我们使用贪心实现的局部最优解;那么对于全局来说,分到的糖果数目其实需要满足让相邻小孩评分高的分到的糖果数目最多这一条件,也就是达到全局最优。
核心代码:
class Solution {
public:
int candy(vector<int>& ratings) {
vector<int> candySend(ratings.size(),1);
//两次遍历,从左到右+从右到左
for(int i=1;i<ratings.size();i++){
if(ratings[i]>ratings[i-1]) candySend[i]=candySend[i-1]+1;
}
for(int i=ratings.size()-2;i>=0;i--){
if(ratings[i]>ratings[i+1]) candySend[i]=max(candySend[i+1]+1,candySend[i]);
}
int result = 0;
for(int i=0;i<candySend.size();i++){
result+=candySend[i];
}
return result;
}
};
总结
对于动态规划和贪心算法我们需要弄懂二者的区别以及一些基本的概念,至于在实际答题中如何去使用,如何想到用这些算法我个人感觉可能还是需要多刷题目找找感觉。在算法的入门学习中,荔枝还是侧重于将基本概念弄清楚,至于典型题目的总结和归纳可能需要更加深入的学习下去才能写好吧。
今朝已然成为过去,明日依然向往未来!我是小荔枝,在技术成长的路上与你相伴,码文不易,麻烦举起小爪爪点个赞吧哈哈哈~~~ 比心心♥~~~