动态规划(详解 带例子)

动态规划之实例一

如果我们有面值为1元、3元和5元的硬币若干枚,如何用最少的硬币凑够11元? (表面上这道题可以用贪心算法,但贪心算法无法保证可以求出解,比如1元换成2元的时候)

首先我们思考一个问题,如何用最少的硬币凑够i元(i<11)?为什么要这么问呢? 两个原因:1.当我们遇到一个大问题时,总是习惯把问题的规模变小,这样便于分析讨论。 2.这个规模变小后的问题和原来的问题是同质的,除了规模变小,其它的都是一样的, 本质上它还是同一个问题(规模变小后的问题其实是原问题的子问题)。

好了,让我们从最小的i开始吧。当i=0,即我们需要多少个硬币来凑够0元。 由于1,3,5都大于0,即没有比0小的币值,因此凑够0元我们最少需要0个硬币。 (这个分析很傻是不是?别着急,这个思路有利于我们理清动态规划究竟在做些什么。) 这时候我们发现用一个标记来表示这句“凑够0元我们最少需要0个硬币。”会比较方便, 如果一直用纯文字来表述,不出一会儿你就会觉得很绕了。那么, 我们用d(i)=j来表示凑够i元最少需要j个硬币。于是我们已经得到了d(0)=0, 表示凑够0元最小需要0个硬币。当i=1时,只有面值为1元的硬币可用, 因此我们拿起一个面值为1的硬币,接下来只需要凑够0元即可,而这个是已经知道答案的, 即d(0)=0。所以,d(1)=d(1-1)+1=d(0)+1=0+1=1。当i=2时, 仍然只有面值为1的硬币可用,于是我拿起一个面值为1的硬币, 接下来我只需要再凑够2-1=1元即可(记得要用最小的硬币数量),而这个答案也已经知道了。 所以d(2)=d(2-1)+1=d(1)+1=1+1=2。一直到这里,你都可能会觉得,好无聊, 感觉像做小学生的题目似的。因为我们一直都只能操作面值为1的硬币!耐心点, 让我们看看i=3时的情况。当i=3时,我们能用的硬币就有两种了:1元的和3元的( 5元的仍然没用,因为你需要凑的数目是3元!5元太多了亲)。 既然能用的硬币有两种,我就有两种方案。如果我拿了一个1元的硬币,我的目标就变为了: 凑够3-1=2元需要的最少硬币数量。即d(3)=d(3-1)+1=d(2)+1=2+1=3。 这个方案说的是,我拿3个1元的硬币;第二种方案是我拿起一个3元的硬币, 我的目标就变成:凑够3-3=0元需要的最少硬币数量。即d(3)=d(3-3)+1=d(0)+1=0+1=1. 这个方案说的是,我拿1个3元的硬币。好了,这两种方案哪种更优呢? 记得我们可是要用最少的硬币数量来凑够3元的。所以, 选择d(3)=1,怎么来的呢?具体是这样得到的:d(3)=min{d(3-1)+1, d(3-3)+1}。

<span style="color:#333333;">package com.dp;
</span><span style="color:#ff0000;">//应该可以优化,求大神指教</span><span style="color:#333333;">
public class TheMinestCoin {

	/**
	 * @param args
	 */
	// 动态规划  所得出的值与前面的相关,即当前子问题的解由上一问题推出,由上一状态迁移到当前状态,受前面状态的影响
	//这是与分支法的本质区别 分治法没有状态迁移  分治法的子问题不受前面结果的影响
	/*
	 * 当硬币为零时
	 * d(0)=0
	 * d(1)=d(1-1)+1
	 * d(2)=d(2-1)+1
	 * d(3)=d(3-1)+1
	 * 
	 * d(3)=d(3-3)+1;
	 * d(3)=min{d(3-1)+1,d(3-3)+1};//取硬币个数的最小值 动态规划
	 */
	public static void main(String[] args) {
		int a[] = { 1, 3, 5 };

		// 最少需要几枚硬币
		int count=11;
		NumberOfCoin(a, count);
	}

	private static void NumberOfCoin(int[] a, int count) {
		int Min[]=new int[count+1];//储存该硬币所需硬币的最小数目
		Min[0]=0;//
		int minCoin=0;//
		for(int i=1;i<Min.length;i++)
		{
			minCoin=i ;//i是不可变的,每一个都是和前一个相关的
			/*
			 * 求出了最小硬币的个数
			 */
			for(int j=0;j<a.length;j++)
			{
				if(a[j]<=i && Min[i-a[j]]+1<minCoin)// 此处</span><span style="color: rgb(51, 51, 51); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;">d(3)=min{d(3-1)+1,d(3-3)+1}</span><span style="color:#333333;">
				{
					minCoin=Min[i-a[j]]+1;
				}
			}
			System.out.println(minCoin + "   i="+i);
			Min[i]=minCoin;//时间复杂度为O(n^2)
		}
		System.out.println(Min[11]);
	}

}
</span>
实例2 动态规划之最长递增子序列

<span style="font-size:14px;">package com.dp;

public class TheLargestIncreamentSubString {

	/**
	 * 假设序列为 5,3,4,8,6,7
	 * d(0)=1;
	 * d(1)=1;因为5>3
	 * d(2)=d(1)+1;4>3
	 * d(3)=d(2)+1;
	 * d(4)=1;因为6<8;
	 * d(5)=d(4)+1;因为7>6 
	 * 则最大递增子序列为d(i) = max{1, d(j)+1},其中j<i,A[j]<=A[i];
	 * 
	 */
	public static void main(String[] args) {
		int a[]={5,3,4,8,6,7};//
		int LIS[]=new int[a.length];//全部都是负数时怎么样??
		for(int i=0;i<a.length;i++)
		{
			LIS[i]=1;
			for(int j=0;j<=i;j++)
			{
				if(a[j]<a[i] && LIS[j]+1>LIS[i])
				{
					LIS[i]=LIS[j]+1;
				}
			}
		}
		int max=LIS[0];
		for(int i=1;i<LIS.length;i++)
		{
			if(LIS[i]>max)
			{
				max=LIS[i];
			}
		}
		System.out.println(max);
		//时间复杂度为o(n^2);
		//优化
		LISS(a);
	}

	private static void LISS(int[] a) {
		//前面的是顺序查找,我们改为二分查找,这样就可以把时间复杂度降低到nlogn
		int LIS[]=new int[a.length];
		LIS[0]=-10000;//假设所有数组中的元素都比该数大(标识数)
		LIS[1]=a[0];//开始时
		int low,mid,high;
		int len=1;
		for(int i=0;i<a.length;i++)
		{
			low=0;
			high=len;//LIS数组当前数组有元素的实际长度即 0和1
			while(low<=high)
			{
				mid=(low+high)>>1;
			    if(a[i]>LIS[mid])
			    {
			        low=mid+1;
			    	
			    }else
			    {
			    	high=mid-1;
			    }
			}
			LIS[low]=a[i];
			if(low>len)len++;
		}
		for(int i=1;i<=len;i++)
		{
			System.out.print(LIS[i]+"  ");
		}	
	}	
</span><span style="font-size:24px;">}
</span>
3.最大子数组和

public class TheLargestSubHe {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//int a[]={-5,2,7,-4,10,3,-2,1}; 
		int a[]={1,-2,3,10,-4,7,2,-5}; 
        MaxSubSum(a);
	}

	private static void MaxSubSum(int[] a) {
		int nAll=0,ntotal=0;
		for(int i=0;i<a.length;i++)
		{
			ntotal=max(ntotal+a[i],a[i]);
			nAll=max(ntotal,nAll);
		}
		System.out.println(nAll);
	}

	private static int max(int i, int j) {
		// TODO Auto-generated method stub
		return i>j?i:j;
	}

}

最长公共子序列

引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。

递归式


图解



public class TheLargestCommonMehtod {
	static int count = 0;

	public static void main(String[] args) {
		String a = " abcbdab";
		String b = " bdcaba";
		
		int c[][] = new int[a.length()][b.length()];
		// 最长公共子序列为 而不是最长公共子串
		theLCS(a, b, c);
		// 打印输出最长公共子序列
		println(a.length()-1, b.length()-1, a, c);
		///System.out.println("*******************************");
		for(int i=1;i<c.length;i++)
		{
			for(int j=1;j<c[0].length;j++)
			{
				System.out.print(c[i][j]+" ");
			}
			System.out.println();
		}
	}

	private static void println(int i, int j, String a, int[][] c) {
		if (i == 0 || j == 0)
			return;
		if (c[i][j] == 1) {
			println(i - 1, j - 1, a, c);
			System.out.print(a.charAt(i) + " ");
			
		}
		if (c[i][j] == 2) {
			println(i - 1, j, a, c);
		} else if(c[i][j]==3){
			println(i, j - 1, a, c);
		}
	}

	private static void theLCS(String a, String b, int[][] m) {
		// System.out.println(a.length());
		if (a == null || b == null || a == " " || b == " ")
			return;
		int c[][] = new int[a.length()][b.length()];// 默认值都为零
		//都从1开始 默认忽略掉第一个元素了,所以我在字符串中添加了一个空格,代表第一个相同,但不参与结果
		for (int i = 1; i < a.length(); i++) {
			for (int j = 1; j < b.length(); j++) {
				if (a.charAt(i) == b.charAt(j)) {
					c[i][j] = c[i - 1][j - 1] + 1;
					m[i][j] = 1;
				} else if (c[i - 1][j] > c[i][j - 1]) {
					c[i][j] = c[i - 1][j];
					m[i][j] = 2;
				} else {
					c[i][j] = c[i][j - 1];
					m[i][j] = 3;
				}
			}
		}
		for (int i = 1; i < a.length(); i++) {
			for (int j = 1; j < b.length(); j++) {
				System.out.print(c[i][j] + "  ");
			}
			System.out.println();
		}

	}
}




评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值