大學期間,接觸的第二個大類算法。然而當時止於知識積累,以爲這個算法奇妙又有魅力可是不會寫。今細讀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; } } } } } }
任何難題,只要害怕,你就解不出來。任什麼時候候不要對本身說,難。