递推:此时的最优必然是此前所推最优与此时可选方案的比较
1.必然有比较,即有多种方式达到目标。
2.必然与此前最优解有关。
3.必然从0到n,先看小最优再看要求最优。
1.确认dp数组与下标
int dp[i];
int dp[i][j];
明确dp与下标的含义,与关系。
2.递推公式
找最优;
dp[i] = min(dp[i-1],dp[i-2]);
遍历所有路径;
dp[i][j] = dp[i-1][j] + dp[i][j-1];
得到dp数组的途径,一般为两种。选择题目要求最优解(类比贪心局部最优推全局最优)。
3.dp数组如何初始化。
dp[0] = 0;dp[1] = 0;
for(int i=0; i<n; i++)
{
dp[0][i] = 0;
}
一般初始化dp[0]与dp[1],其值可能为0,1.
4.遍历顺序
for(i=0; ;){
for(j=0; ;)
}
5.举例推导dp函数。
遇到的难题:力扣:96-不同的二叉搜索树。
需要熟悉二叉搜索树和动态规划的概念。
1.必然从0到n,想求dp[n],先去dp[0]......。
2.dp与下标关系:下标为组成结点,dp为有多少种组成方式。
这题不同之点在于一棵树有两个子树。及需要排列出左右子树的各种组成结点数再相加。
左子树的情况 * 右子树的情况为这一种情况的 种类。
for(int i=1; i<=n; i++)
{
for(int j=1; j<=i; j++)
{
dp[i] = dp[i] + dp[j-1]*dp[i-j];
}
}
至于二叉搜索中的搜索不用我们担心,我们只知道结点数,其值将由系统自动分配。
动规:01背包问题
力扣416:分割等和子集问题
确认dp数组以及下标含义(套入01背包),三个要素,容量,重量,条件(是刚好装满,还是价值最高):
二维01背包:dp[i][j];
i:存入的第几个数,j:背包的容量。条件:刚好装满(即最大重量 ==最大容量 )。
dp数组的初始化:
分别判断i=0,与j=0的情况,考虑初始化。
当j=0时,重量为0,当i = 0时,不装都为0,能装都为nums[i]
则全初始化为0;
状态方程:
经典01背包状态方程:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-nums[i]]);
遍历顺序:
for(i=1; i<numsSize; i++)//先遍历物品,每个物品只被使用一次。
{
for(j=0; j<=sum/2; j++)//再遍历容量。
{
if(j>=nums[i])
{
dp[i][j] = max(dp[i-1][j],dp[i-1][j-nums[i]]+nums[i]);
}
else
{
dp[i][j] = dp[i-1][j];
}
}
}
二维与一维需要注意:
遍历顺序的不同:一维只能先遍历物品再遍历容量。且容量由大到小遍历,避免装入重复元素。
又一种01背包类型题,从找背包容量到状态方程都有很大不同
定义dp数组
明确dp数组及其下标含义
dp【】表示有多少种方法,下标标识背包容量为多少时有多少种方法,i表示处理第i个物品时不同背包容量会得到的总次数。
背包容量为x-(sum-x) = target -》 x = (sum+taget)/2;
所求的即是当 j = x 时,且遍历完 i 后,得到的次数。
比较奇特的状态方程
for(i=0; i<numsSize; i++)
{
for(j=(target+sum)/2; j>=nums[i]; j--)
{
dp[j] += (dp[j-nums[i]]);
}
}
return dp[(target+sum)/2];
dp【j】 = 上一个物品在不考虑当前物品本身容量情况下的最多次数+上一个物品在最大容量时的最多次数。
又思考了一天后,明白这个状态方程。
dp[j](当出现新的物品时,将这个物品放或不放进去)
= dp[j](不放,即没有变化,上一个物品已经可以组成的可能性和次数)
+ dp[j-nums[i]](放入,即产生了新的变化,减去了新物品的重量后,还可以有多少种可能,因为加入了新元素,则都是新的装满背包的可能性)。
完全背包:
1)求组合数是外层循环物品,内层遍历容量
2)求排列数(不同顺序也可),先遍历外层容量,再遍历内层物品。