动态规划是一个很重要的算法,很重要。。。 记得去年参加招聘的时候,大多数的笔试题都有动态规划。当时年轻呀。。。 这几天把动态规划好好练习一下。
维基百科对动态规划的定义是:动态规划是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推的方式去解决。
动态规划的本质是拆分问题,定义问题状态和状态之间的关系,问题状态就是动态规划可以拆分成的子问题,状态之间的关系就是动态规划子问题之间的联系,就是我们常说的状态转移方程。
说实话,我现在对定义的理解还是很浅。。。 还需要多做题。 还是直接上题目吧。
首先是最简单的,动态规划解决int型数组最长公共子序列(子序列不用连续,子串才是连续的)的问题。
比如有数组{1,3,2}他的最长子序列是{1,2},长度为2
首先,分析这个问题能不能用动态规划来做,就是分析这个问题能不能拆分成小问题来做。
首先,这个问题是可以拆分成小问题的,对于数组sums{0,1,2...n}来说,求他的最长子序列,可以先求sums{0,1,2,...n-1]的最长子序列,然后再求sums的最长子序列。
说道拆分子问题,肯定很多人想到递归了。动态规划能解决的问题,一般来说递归也能解决,不过动态规划的好处就是当大问题的解依赖小问题的解的时候,动态规划会有个状态保护数组,来保存已经有解的小问题,而不像递归那样做很多次重复计算,最经典的就是斐波纳斯数列了。
假设P(i)是数组sums{0,1,2...i}的最长子序列的大小,那么P(N)的值是多少呢?首先,P(N)依赖什么,P(N)依赖于P(0),p(1)...p(n-1)的,这时候还需要一个数组M(i)存储数组sums{0,1,2...i}的最长子序列大小是P(i)的时候,这个最长子序列中元素的最大值,那么以P(i)和M(i)为基础,就可以算出P(N)在依赖P(i)的情况下的值了。
如果sums[n]>M(i),那么P(n)=p(i)+1, M(n)=sums[n]
否则:P(n)=p(i), M(n)=M[i]
干说可能不太好理解,我比较喜欢不容易理解的问题用比较轻量的例子来演算一遍:
比如对于数组{1,3,2}来说,初始情况下,P(0) = 1,M(0) = 1(这个不用解释了吧。。。 初始情况下,对于数组{1}来说,最长子序列大小为1,最长子序列中最大元素为1);
然后求P(1),这时候n=1,i=0,对比sums[1]和M(0)的大小,发现sum[1]>M[0],所以P(1) = p(0)=1,m(1) = m(0) = 1;
然后根据i=0,判断P(2)
首先根据sums[n]>M(i), 知道p(2) = p(i) +1 = 2,M(2) = sums[2] = 3,可以看到这就是结果了!
逻辑就是这样,求p(n)的时候,分别根据(0,1,2。。。n-1)求出来所有可能的p(n)值,然后取最大的即可。个人觉得这样描述会比数学公式描述看得清楚点。。。。
最后还是上代码:
/** * DPTest : 动态规划练习 * * @author xuejupo [email protected] * * create in 2016-1-14 下午6:59:34 */ public class DPTest { /** * main: * * @param args * void 返回类型 */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(dpIncreasing(new int[] { 1, 2, 3, 4, 6, 7, 3, 8 })); } /** * dpIncreasing: 动态规划解决最长递增自序列问题 求int数组的最长递增自序列大小,如{1,2,3,4,6,2,3,8},返回6 * * @param nums * @return int 返回类型 */ private static int dpIncreasing(int[] nums) { // 边界条件 if (nums == null || nums.length == 0) { return 0; } // 帮助数组,保存特定位置的最长自序列大小 int[] help = new int[nums.length]; // 初始化 help[0] = 1; // 帮助数组,保存以j结束的最长自序列中最大值 // 如{1,2,3,4,6,2,3,8},自序列{1,2,3,4,6}的最大值为6 int[] help1 = new int[nums.length]; // 初始化 help1[0] = nums[0]; for (int i = 1; i < nums.length; i++) { // 求以i结束的数组的最长自序列 // 状态转换方程:max(help(0->i-1)+1) int max = 0; // 内层循环的逻辑是:根据i所在位置的值,从第1个到第i-1个子数组中找到最长的递增自序列 for (int j = 0; j < i; j++) { int maxTemp = 0; if (help1[j] < nums[i]) { maxTemp = help[j] + 1; } else { maxTemp = help[j]; } if (max < maxTemp) { max = maxTemp; help1[i] = Math.max(nums[i], help1[j]); } } help[i] = max; } return help[nums.length - 1]; } }
结果:
7