动态规划(dp):将一个复杂的问题分解成若干子问题,通过综合子问题的最优解来求得原问题的最优解。虽然dp的理解很像分治和贪心,但它们之间还是有区别的。
分治要求每一个子问题不重叠,例如快速排序的实现。而dp要求子问题必须重叠,即子问题会重复出现。另外,分治解决的问题不一定是最优化问题,而dp解决的问题一定是最优化问题。
贪心就像一个链条,在每个节点上都只选择当前结果的最优值,而不去关心其他值,最后只会形成一条单链。而dp则会考虑所有子问题,并选择继承最优解。例如数塔问题:贪心是自上而下每次都选择当前值左下和右下更大的那个值,虽然每次决策的结果都看似是对的,但对于整个问题而言,并不一定正确。而dp则是将所有可能性都考虑进去,然后再选择最优的那一个。这样的话如果自下而上遍历,每一次决策都是考虑过所有结果的最优值。从而答案得以保证。
dp经典问题——背包问题
一.01背包问题:
例如:有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每件物品都只有1件。
考虑对第i件物品的选择策略,有两种策略:
①不放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v的背包中所能获得的最大价值,即dp[i-1][v];
②放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值,也即dp[i-1][v-w[i]]+c[i].
列出状态转移方程
dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]]+c[i]);(dp[i][v]表示将前i件物品恰好放入容量为v的背包中所能获得的最大价值)
注:如果dp为二维数组,那么先遍历物品再遍历背包或者是先遍历背包再遍历物品都可以。
且v枚举方向正序和逆序都可以。
由于dp[i][v]的值只取决于dp[i-1][]所以可以将空间复杂度优化,即可以将数组dp优化为一维。这种技巧为滚动数组。
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
注:当dp为一维数组时,只可以先遍历物品,再遍历背包。且v枚举方向必须为逆序。因为逆序时后面的数据不会影响前面,从而不会重复计算。
二.完全背包问题:
有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每件物品都有无穷件。
思路和前面一样,二维数组的状态转移方程:
dp[i][v]=max(dp[i-1][v],dp[i][v-w[i]]+c[i]);
唯一区别就是max的第二个参数将dp[i-1]换成了dp[i];
一维状态转移方程:
dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
一维形式和01背包完全相同,但此时v的枚举方向是正序。