动态规划学习的第一天
本篇博客内容参考学习了https://labuladong.gitbook.io/algo/中的内容(建议大家可以去看这个,里面算法干货很多)。
动态规划问题的一般形式就是求最值,例如求最长递增子序列,最小编辑距离之类的。其核心问题就是穷举(我个人认为其实计算机处理大部分问题都是通过穷举来实现的,只不过加了一些优化的过程在其中)。
动态规划的三要素:重叠子问题、最优子结构、状态转移方程。
我们在利用递归解决一些问题的时候,会经常出现重复计算的问题,这些就可以被看成是重叠子问题。在递归中可以加个备忘录,将每个子问题的答案添加到这个备忘录中,然后每次计算的时候先去备忘录查询是否之前已经解决过这个问题。在动态规划中可以使用dp table去解决。这种带备忘录的递归方法与动态规划相比,最大的区别是前一种是自顶向下的,而后一种是自底向上的。
要符合最优子结构,子问题之间必须是相互独立的,就是一个子问题的结果是不会影响到另外子问题的结果。另外最优子结构并不是动态规划专有的,它是很多问题都有的一种特定性质。它就是指从子问题的最优结果中推出更大规模问题的最优结果。
状态转移方程直接代表着暴力解法。
如何列出正确的状态转移方程:
1.确定【状态】,也就是原问题和子问题中变化的变量。
2.确定dp函数的定义。
3.确定选择并择优,也就是对于每个状态,可以做出什么选择改变当前状态。
dp数组的遍历方向:在动态规划中dp数组的遍历顺序有正向遍历、反向遍历,有的时候还会有斜向遍历。
主要需要注意的是这两个点:
1、遍历的过程中,所需的状态必须是已经计算出来的。
2、遍历的终点必须是存储结果的那个位置。
经典问题:凑零钱
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5]
, amount = 11
输出:
3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2]
, amount = 3
输出: -1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
动态规划的方法。(利用c语言实现)
//dp[i]=x表示当目标金额为i时,至少需要x枚硬币。
int coinChange(int* coins,int coinsSize,int amount){
int *dp=(int*)malloc(sizeof(int)*(amount+1));
for(int i=0;i<=amount;i++)
{
for(int j=0;j<coinSize;j++)
{
if(i-coins[j]<0) continue;
dp[i]=fmin(dp[i],1+dp[i-coins[j]]);
}
}
int res=(dp[amount]==amount+1)?-1:dp[amount];
free(dp);
return res;
}