java-动态规划练习1

       动态规划是一个很重要的算法,很重要。。。 记得去年参加招聘的时候,大多数的笔试题都有动态规划。当时年轻呀。。。   这几天把动态规划好好练习一下。

       维基百科对动态规划的定义是:动态规划是通过拆分问题定义问题状态和状态之间的关系,使得问题能够以递推的方式去解决。

       动态规划的本质是拆分问题,定义问题状态和状态之间的关系,问题状态就是动态规划可以拆分成的子问题,状态之间的关系就是动态规划子问题之间的联系,就是我们常说的状态转移方程。

       说实话,我现在对定义的理解还是很浅。。。    还需要多做题。   还是直接上题目吧。

       首先是最简单的,动态规划解决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

 

猜你喜欢

转载自709002341.iteye.com/blog/2271287