大学期间,接触的第二个大类算法。然而当时止于知识积累,以为这个算法奇妙又有魅力可是不会写。今细读topcoder的一篇动态规划,重新手到专家的翻译版博客,完成了入门修炼。算法
动态规划(Dynamic Programming)它是一种考虑问题的思想(对于初级的我来讲),他没有必定的范式来总结。它很是很是有意思,它的应用随处可见,好比地图中搜索a到b的路径。spring
它的思想整体来讲会分几个要点:1.这个问题是个迭代的问题,即求d(i)的解必须在d(i-1)的基础上来寻找;2.他存在重叠子问题,好比找钱问题,5块钱找的最少硬币数会在4块钱的基础上寻找;3.它存在状态转移方程,诸如d(i)=max{d(i-1)+1,a}此类形式的最佳状态转移;4.它的结果是全局最优解,由于每次子问题的解都是从子子问题的解集中取最优并计算出当前最优解,这与贪心算法有着细小的区别,由于贪心算法是从当前节点往大解集进发时候选择最优而不是从子问题中总结出最优。数组
初级解决的是一维的问题,刚开始的时候会有点迷茫,由于分析问题的过程是别人写的,有不少看不懂。在本身尝试分析的时候总结出来了一点经验,循着经验和感受,分析问题愈来愈easy。app
talk is cheap,show me the code!ide
这句话是在这篇博客上出现的,事实上不少人写算法博客就只贴代码,忘了写详细的分析过程,这样会颇有碍于表达能力。因此我既要贴代码也要贴废话。函数
这个问题是入门第一个问题,咱们假设有1,3,5这三种面值的硬币,给定一个数,最少使用的硬币组合是什么?好比13测试
分析:这是一个典型又典型的动态规划问题,首先13块钱能够考虑是12块钱的最优解上加上1,为何呢?咱们从i=1开始分析:1块钱的时候只用一个硬币是显然的;i=2时候,由于面值仍是只有1的硬币可用,因此用两个硬币;i=3的时候,就会有新的面额能够用了,因此它的解是:min{d(2)+1,d(3-3)+1}(d(0)=0),显然它的解是1。因此i=4的时候,是d(4) = d(4-1)+1,d(4)=2。spa
思惟过程:欲求d(13)必须知道d(12)的解,加上1。
翻译
计算过程:i=1,计算到i=13rest
code:
package DynamicProgramming; /** * 存在1块 3块 5块面值的纸币 * @author MacBook * */ public class LessMoney { //mix{d(i-1) + 1} i<3 //min{d(i-3) + 1,d(i-1) +1} 3=<i<5 //min{d(i-5) + 1,d(i-1) + 1} i>=5 public int counting(int money){ int[] state = new int[money+1]; state[0] = 0; for(int i=1 ; i<=money; i++){ int before = getBeforeState(i); int number1 = state[before]+1; int number2 = state[i-1]+1; int min = Math.min(number1, number2); state[i] = min; } for(int a : state) System.out.print(a+" "); System.out.println(); return state[money]; } //计算上态函数 d(i-m) public int getBeforeState(int currentState){ if(currentState < 3 && currentState >0){ return currentState -1; }else if(currentState >= 3 && currentState < 5){ return currentState -3; }else if(currentState >= 5) return currentState -5; else { return -1; } } public static void main(String args[]){ LessMoney lm = new LessMoney(); System.out.println(lm.counting(15)); } }
这里还有一个问题,就是高额的状态转移方程,为何是-5而不比较-3呢?有点点想不通。
这是入门遇到的第一个坎,由于这个问题很经典,一不当心就理解错误了。这个问题是求数组中的递增子段的长度,什么意思呢?好比1,3,5,6这样的子段。可是1,3,2,5,6它的最长非降子段倒是1,3,5,6。由于它所谓的子段是不连续也无所谓的!形成我写了一个O(n)的计算方法,别人说最快也是O(n*logn)我深表怀疑。
分析:欲求d(i)的值,必须求出d(i-i)的值(套路)。
以1,3,2,5,6为例:i=1时候,最小子段为1;i=2,子段1,3;i=3,这就开始注意了,由于A[i]=2,它小与A[i-1],因此出现状态转移方程d(i)=max{d(j)+1,1},这里仍然是1,3子段;i=4时候,1,3,5;i=5时,1,3,5,6。
这里我采用逆序查找最长子段,当i时候,从i->0查找以A[i]为最大元素的子段。
它的常规算法是O(n*n)复杂度,改进算法是O(n*logn),改进算法的思路是:维护一个单调序列(栈),当元素大于栈顶元素时候入栈,当元素小于栈顶元素时候,二分查找比它大的第一个元素,替换之。
code:
package DynamicProgramming; /** * longest increasing subsequence * A[1],A[2],...,A[n] 求出最长非降自段和 * @author MacBook * */ public class LIS { /* * 状态转移方程:d(i) = max{1,d(i-1)+1},其中,A[i-1]<=A[i] */ public static void main(String args[]){ LIS l = new LIS(); int[] subsequence={5,3,4,8,6,7,2,9,10,21,15}; System.out.println(l.counting3(subsequence)); } //连续非降子序列 public void counting(int[] subsequence){ int len = 0; int [] state = new int[subsequence.length];//状态集合 for(int i=0;i<subsequence.length ;i++){ if(i == 0){ len++; state[i] = len; }else{ if(subsequence[i]>subsequence[i-1]) { len++; }else{ len = 1; } int statecode = Math.max(state[i-1], len); state[i] = statecode; } } for(int a:state){ System.out.print(a+" "); } System.out.println(); } //若计算A[j]的最大子序列 则计算A[j-1]全部元素做为最大元素的字段和 加上当前元素并记录--算法复杂度O(n*n) public void counting1(int[] subsequence){ int lenTemp = 1; int[] liss = new int[subsequence.length]; liss[0] = 1; for(int i=0;i<subsequence.length;i++){ for(int j = i;j>=0;j--){ if(subsequence[j]<subsequence[i]) { if(lenTemp < liss[j]+1) { lenTemp = liss[j]+1; } } } liss[i] = lenTemp; lenTemp = 1; } for(int a:liss) System.out.print(a+" "); System.out.println(); } //改进算法 使用一个栈来维护一个单调序列,读到sub的元素时候: //1.若是比栈顶元素大,则放在栈顶;2.若是比栈顶元素小,则二分查找替换单挑序列中比它大的第一个元素 public int counting3(int[] subsequence){ int [] array = new int[subsequence.length]; //初始化栈 array[0] = subsequence[0]; int index = 0; for(int i=0;i<subsequence.length;i++){ if(subsequence[i]>array[index]){ array[++index] = subsequence[i]; } else if(subsequence[i]<array[index]){ int location = bisection(array,subsequence[i],0,index); //若是相应位置的值等于查询值 则替换它下一个元素 array[location] = subsequence[i]; } } return index+1; } //二分查找查询比元素大的位置 //寻找比sign大的第一个元素,若是存在与sign相等的元素,再行断定 public int bisection(int[] array,int sign,int start,int end){ int mid = 0; while(start<end) { mid = (start + end)/2; if(sign>array[mid]){ start = mid; }else{ end = mid; } if(start+1 == end) { mid = end; break; } } return mid; } }
这个东西让我查了很久(由于我没直接点进博客的连接,很差的习惯),起初我觉得是z字形算法,后来是topcoder的竞赛题,乍一看,挺简单,由于和LIS的机制是同样的。它的问题是这样的:zigzag数组,是一个正负增加率交替的数组,1,5,2,3这样的。更坑爹的是2,8,3,4,5,7这样的也存在zigzag数组。它是2,8,3,5。像LIS同样,使用动态规划计算它的长度。
分析:仍然套路,欲求d(i)必先求d(i-1),不一样的是须要记录i-1元素的最大长度的当前增加率。好比上例,计算到3这个元素的时候,它以前是8,因此它的当前增加率是positive(正的)。
code:
package DynamicProgramming; /** * 2003 TCCC Semifinals 3 * 题目:给定的一个序列,他的增加是按照正负正负交替的,他叫作zigzag序列, * 程序实现输入一个序列计算他的最长zigzag序列,包括它的子序列,最后返回最长zigzag序列的长度。 类比:lis * * @author MacBook * Problem Statement * A sequence of numbers is called a zig-zag sequence if the differences * between successive numbers strictly alternate between positive and * negative. The first difference (if one exists) may be either positive * or negative. A sequence with fewer than two elements is trivially a * zig-zag sequence. For example, 1,7,4,9,2,5 is a zig-zag sequence * because the differences (6,-3,5,-7,3) are alternately positive and * negative. In contrast, 1,4,7,2,5 and 1,7,4,5,5 are not zig-zag * sequences, the first because its first two differences are positive * and the second because its last difference is zero. Given a sequence * of integers, sequence, return the length of the longest subsequence * of sequence that is a zig-zag sequence. A subsequence is obtained by * deleting some number of elements (possibly zero) from the original * sequence, leaving the remaining elements in their original order. */ public class Zigzag { public static void main(String args[]){ Zigzag z = new Zigzag(); int [] subsequence ={70, 55, 13, 2, 99, 2, 80, 80, 80, 80, 100, 19, 7, 5, 5, 5, 1000, 32, 32}; z.counting1(subsequence); } //欲计算d[i]的值,须要计算d[0]-d[i-1]中最大值(与a[i]组合造成zigzag序列的)+1 //d(i) = max{d(j)+1},j∈(0,i-1) && 造成zigzag序列 public void counting1(int[] subsequence) { int [] pre = new int[subsequence.length];//记录本元素上一个zigzag序列元素 int [] liss = new int [subsequence.length];//计算当前元素最长zigzag序列 boolean [] ispositive = new boolean[subsequence.length];//计算当前元素的下一个增加率 pre[0] = 0; liss[0] = 1; //通过测试 --固定起手正负会影响最终结果的正确性,因此初始化的时候须要在i=1时候计算正负 if(subsequence[1]-subsequence[0] < 0) ispositive[0] = false; else if(subsequence[1] > 0) ispositive[0] = true; for(int i=1;i<subsequence.length;i++){ for(int j=0;j<i;j++){ //若是出现zigzag的特征: //1.判断当前序列+1是否大于已存序列,大于则存 //2.将增加特征取反 //3.前驱标记j存入数组 if(subsequence[i]-subsequence[j]>0 && ispositive[j] == true){ if(liss[j]+1>liss[i]){ liss[i] = liss[j]+1; ispositive[i] = false; pre[i] = j; } }else if(subsequence[i]-subsequence[j]<0 && ispositive[j] == false){ if(liss[i]+1>liss[i]){ liss[i] = liss[j]+1; ispositive[i] = true; pre[i] = j; } } } } for(int a:pre) System.out.print(a+" "); System.out.println(); for(int a:liss) System.out.print(a+" "); System.out.println(); for(boolean a:ispositive) System.out.print(a+" "); System.out.println(); } }
BadNeighbors
问题描述:当前节点捐钱,邻居节点则不会捐钱,计算数组中可以募集的最大捐款数。注意,首位和尾位是一对邻居。
分析:LIS的变式,难点是首位和尾位的计算,由于计算d(i)的时候让然会加入d(0),因此根据事理分析,它的最优且合理的解,存在于i-1位。
code:
package DynamicProgramming; /** * tips:这个题目的大意是输入一个序列,按照跳位的方式求最大加起来的数值(由于邻居捐款以后它的临位不想捐款) * 首位和末尾是邻居 * @author MacBook * Problem Statement * * The old song declares "Go ahead and hate your neighbor", and the * residents of Onetinville have taken those words to heart. Every * resident hates his next-door neighbors on both sides. Nobody is * willing to live farther away from the town's well than his neighbors, * so the town has been arranged in a big circle around the well. * Unfortunately, the town's well is in disrepair and needs to be * restored. You have been hired to collect donations for the Save Our * Well fund. * * Each of the town's residents is willing to donate a certain amount, * as specified in the int[] donations, which is listed in clockwise * order around the well. However, nobody is willing to contribute to a * fund to which his neighbor has also contributed. Next-door neighbors * are always listed consecutively in donations, except that the first * and last entries in donations are also for next-door neighbors. You * must calculate and return the maximum amount of donations that can be * collected. */ public class BadNeighbors { public static void main(String args[]){ BadNeighbors b = new BadNeighbors(); int [] subsequence1 ={ 10, 3, 2, 5, 7, 8 }; b.counting1(subsequence1); int [] subsequence2 ={11,15}; b.counting1(subsequence2); int []subsequence3 ={ 7, 7, 7, 7, 7, 7, 7 }; b.counting1(subsequence3); int []subsequence4 ={ 1, 2, 3, 4, 5, 1, 2, 3, 4, 5 }; b.counting1(subsequence4); int []subsequence5 ={ 94, 40, 49, 65, 21, 21, 106, 80, 92, 81, 679, 4, 61, 6, 237, 12, 72, 74, 29, 95, 265, 35, 47, 1, 61, 397, 52, 72, 37, 51, 1, 81, 45, 435, 7, 36, 57, 86, 81, 72}; b.counting1(subsequence5); } //状态转移方程:d(i) = d(j) + a[i],而且j!=i-1 public void counting1(int [] subsequence){ int[] liss = new int[subsequence.length]; int[] pre = new int[subsequence.length]; int max = 0; pre[0] = 0; liss[0] = subsequence[0]; //长度为2的有点特殊 if (subsequence.length > 2) { for (int i = 1; i < subsequence.length; i++) { //不计算邻居捐款 for (int j = 0; j < i - 1; j++) { if (liss[j] + subsequence[i] > liss[i]) { liss[i] = liss[j] + subsequence[i]; pre[i] = j; } } } // 由于最后一个和第一个是邻居 max = liss[subsequence.length-2]; }else if(subsequence.length == 2){ max = Math.max(subsequence[0], subsequence[1]); } for(int a:pre) System.out.print(a+" "); System.out.println(); System.out.println("max="+max); } }
FlowerGarden
有点看不懂题目刚开始,由于不像dp,不事后来仍是解了,一句话总结,区间交集取小者前,全局大者前。
只是一个排序,开花时间和枯萎时间有交集的就把高度小的放前面,全局数组是尽量高的前。
code:
package DynamicProgramming; /** * * @author MacBook * Problem Statement * * You are planting a flower garden with bulbs to give you joyous * flowers throughout the year. However, you wish to plant the flowers * such that they do not block other flowers while they are visible. * * You will be given a int[] height, a int[] bloom, and a int[] wilt. * Each type of flower is represented by the element at the same index * of height, bloom, and wilt. height represents how high each type of * flower grows, bloom represents the morning that each type of flower * springs from the ground, and wilt represents the evening that each * type of flower shrivels up and dies. Each element in bloom and wilt * will be a number between 1 and 365 inclusive, and wilt[i] will always * be greater than bloom[i]. You must plant all of the flowers of the * same type in a single row for appearance, and you also want to have * the tallest flowers as far forward as possible. However, if a flower * type is taller than another type, and both types can be out of the * ground at the same time, the shorter flower must be planted in front * of the taller flower to prevent blocking. A flower blooms in the * morning, and wilts in the evening, so even if one flower is blooming * on the same day another flower is wilting, one can block the other. * * You should return a int[] which contains the elements of height in * the order you should plant your flowers to acheive the above goals. * The front of the garden is represented by the first element in your * return value, and is where you view the garden from. The elements of * height will all be unique, so there will always be a well-defined * ordering. * * tips:输入 : 1.开花高度 2.开花开始时间 3.开花结束时间 * 规则 : 保证最大可能高度在第一位;若是开花周期交叉,则将高度矮的放在高度高的以前;若是周期不交叉,则序列不变。 */ public class FlowerGarden { public static void main(String[] args) { FlowerGarden f = new FlowerGarden(); //测试用例1 int[] height1 = {5,4,3,2,1}; int[] bloom1 = {1,5,10,15,20}; int[] wilt1 = {5,10,14,20,25}; f.counting(height1,bloom1,wilt1,height1.length); for(int a:height1) System.out.print(a+" "); System.out.println(); //测试用例2 int[] height2 = {5,4,3,2,1}; int[] bloom2 = {1,5,10,15,20}; int[] wilt2 = {5,10,15,20,25}; f.counting(height2,bloom2,wilt2,height2.length); for(int a:height1) System.out.print(a+" "); //测试用例3 int[] height3 = {5,4,3,2,1}; int[] bloom3 = {1,5,10,15,20}; int[] wilt3 = {4,9,14,19,24}; f.counting(height3,bloom3,wilt3,height3.length); for(int a:height1) System.out.print(a+" "); //测试用例4 int[] height4 = {1,2,3,4,5,6}; int[] bloom4 = {1,3,1,3,1,3}; int[] wilt4 = {2,4,2,4,2,4}; f.counting(height4,bloom4,wilt4,height4.length); for(int a:height1) System.out.print(a+" "); } //不校验输入-->bloom(相同index)要比wilt的小 public void counting(int[] height,int[] bloom,int[] wilt,int length){ for(int i=0;i<length;i++){ for(int j=0;j<i;j++){ //若是存在交集 if(bloom[i] <= wilt[j]){ if(height[j]>height[i]){ int temp = height[i]; height[i] = height[j]; height[j] = temp; temp = bloom[i]; bloom[i] = bloom[j]; bloom[j] = temp; temp = wilt[i]; wilt[i] = wilt[j]; wilt[j] = temp; } }else{ if(height[j]<height[i]){ int temp = height[i]; height[i] = height[j]; height[j] = temp; temp = bloom[i]; bloom[i] = bloom[j]; bloom[j] = temp; temp = wilt[i]; wilt[i] = wilt[j]; wilt[j] = temp; } } } } } }
任何难题,只要害怕,你就解不出来。任什么时候候不要对本身说,难。