动态规划
一、动态规划思想
动态规划的方法有两种,一种是带备忘录的递归,一种是递推求解。递推求解是先自顶向下再自底向上求解,而带备忘录递归则只有自底向上,其效率会更高。
动态规划算法其实就是填表的过程,在填表的过程中不断的比较与择优。
动态规划算法其基本思想也是将原问题分解成若干个子问题,通过求解子问题得到原问题的解。但与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。
动态规划算法的解题步骤:
(1)确定dp数组以及下标的含义
(2)确定递推公式
(3)dp数组如何初始化
(4)确定遍历顺序
(5)举例推导dp数组(用于debug)
二、0-1背包问题
1、题目:有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
2、步骤:
(1)确定dp数组含义:dp[i][j]表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
(2)确定递推公式:dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]) (比较选与不选的结果)。
(3)dp数组初始化:当背包容量为0时,可以放入的价值为0;当有0件物品可以选时,可以放入的价值也为0。
(4)遍历顺序:先遍历物品在遍历背包容量或者先遍历容量再遍历物品都可,因为根据递推公式,dp[i][j]的值是根据左边或者左上角推出来的,只要确保在计算到dp[i][j]时,其左边或左上角的值都已经计算好即可(一般都是先遍历物品再遍历背包)
3、优化空间复杂度
使用一维数组(滚动数组)来代替二维的dp数组,因为通过递推公式dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])可以发现,dp[i][j]和上一层的数据有关,如果将第i-1层的数据直接拷贝的第i层,那么递推公式可以变成dp[i][j] = max(dp[i][j],dp[i][j-weight[i]]+value[i]),由于第一维都是i,那么就可以降维了。此时,dp[j]数组的定义是当背包容量为j时,价值总和最大为多少。
注意:遍历顺序一定是先遍历物品,再遍历背包容量,从后往前倒序遍历(保证物品只被放入一次)
倒序:由于dp[i][j]是根据上一行的左边的值计算出来的,因此一维数组需要从右往左倒序遍历,才能保证dp[i][j]上一行左边的值不会被覆盖。
4、确定0-1背包问题
(1)物品只能被选择一次 (2)确定背包的体积是什么 (3)确定商品的体积和价值是什么
5、求装满背包的方式
方式就是dp[j-nums[i]]累加
如果是组合数,则说明物品没有先后顺序,{1,2}和{2,1}是一样的,为了不重复计算,遍历顺序应该是先物品再背包容量
如果是排列数,则说明物品是有先后顺序的,{1,2}和{2,1}是不一样的,为了不漏计算,遍历顺序应该是先背包容量再物品
三、完全背包问题
和0-1背包问题的区别是,物品可以被放入多次,因此使用一维数组时,遍历顺序与0-1背包有所不同,是先遍历物品,再遍历背包容量,从前往后正序遍历(保证物品可以被放入多次)
四、子序列问题
dp数组通常定义为以i结尾的序列的结果
子序列定义:由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序,保持相对位置一致。
①连续递增子序列:只和i-1有关
②不连续递增子序列:和前i-1个都有关