1. 程式人生 > >華為校招第三題:字串變換最小費用(動態規劃DP問題)

華為校招第三題:字串變換最小費用(動態規劃DP問題)

題目:
給出兩個字串A,B。將A字串轉化為B字串,轉化一共有兩種方式:刪除連續的n個字元,一次操作費用為2。增加連續的n個字元(增加的字元是什麼由你決定),一次操作費用為n+2。求把A變為B最小費用。

輸入: 
第一行輸入一個正整數T(1 <= T <= 10),表示有T組測試資料。
對於每組測試資料:
兩行字串A, B(字串長度不超過2000,字元僅包含小寫字母)
輸出: 
對於每組測試資料,輸出一行一個整數,表示最小費用。
樣例輸入:   
2
dsafsadfadf
fdfd
aaaaaaaa
bbbbbbbb
樣例輸出:   
7
12

答案提示:
“dsafsadfadf” 變成 “fdfd” 最少的代價的一種方法是:
1. “dsafsadfadf” -> “f” 刪除連續的10個,代價2
2. “f” -> “fdfd” 增加連續的3個(”dfd”),代價為3 + 2 = 5
總共的最小代價為2 + 5 = 7,其他方法的代價都不小於7
“aaaaaaaa” 變成 “bbbbbbbb” 最小代價的一種方法是:
1. “aaaaaaaa” 全部刪除,代價2
2. 增加8個連續的’b’,代價10
總共的最小代價為2 + 10 = 12
注意,有些最優的方案可能要做多次的刪除和增加操作,不限於兩次。

這是一個比較典型的動態規劃的題目:

public class Main {
            public static void main(String[] args) {  
               Scanner sc=new Scanner(System.in);
               int a=sc.nextInt();
               String[] buff=new String[a*2];
               int i=0;
               sc.nextLine();  //為了清除到 nextInt擷取之後的空格
//sc.next()方法是以換行或者空格符為分界線接收下一個String型別變數 while(true) { buff[i]=sc.nextLine(); System.out.println("除錯: "+buff[i]); i+=1; if(a*2==i) break; } for(int j=0
;j<a;j++) { System.out.println(GetMinExpenses(buff[j*2],buff[j*2+1])); } } public static int DEL = -1; public static int ORIGAL = 0; public static int ADD = 1; public static int getAddCount(int f, int type) { int minCost; if (type == ADD) { minCost = f + 1; } else { minCost = f + 3; } return minCost; } public static int getDelCount(int f, int type) { int minCost = 0; if (type == DEL) { minCost = f + 0; } else { minCost = f + 2; } return minCost; } public static int getMin(int a, int b) { return a < b ? a : b; } public static int GetMinExpenses(String aString, String bString) { int[][] f = new int[aString.length() + 1][bString.length() + 1];//f[i][j] 從a[i] -> b[j]的最小代價 f[0][0] = 0; int operator[][] = new int[aString.length() + 1][bString.length() + 1];//用於記錄操作 for (int i = 1; i < aString.length() + 1; i++) { f[i][0] = 2; operator[i][0] = DEL; } for (int i = 1; i < bString.length() + 1; i++) { f[0][i] = 2 + i; operator[0][i] = ADD; } int type = ORIGAL; for (int i = 1; i < aString.length() + 1; i++) { // System.out.println(); for (int j = 1; j < bString.length() + 1; j++) { // System.out.print(minCost + "_" + operator[i][j] + " "); int tempType; int cost = 0; if (aString.charAt(i - 1) != bString.charAt(j - 1)) { cost = 5; } int minCost; int delCount = getDelCount(f[i - 1][j], operator[i - 1][j]); /* * * // getDelCount(f,type) if (type == DEL) { minCost = f + 0; } else { minCost = f + 2; } // 看她 前面的 是不是 DEL,如果是,那個再把這個字母代價 刪到 ,需要增加的代價就為 0 */ int addCount = getAddCount(f[i][j - 1], operator[i][j - 1]); /* * //getAddCount(f,type) if (type == ADD) { minCost = f + 1; } else { minCost = f + 3; } */ if (delCount >= addCount) { operator[i][j] = ADD; minCost = addCount; } else { operator[i][j] = DEL; minCost = delCount; } if (minCost > f[i - 1][j - 1] + cost) { operator[i][j] = ORIGAL; minCost = f[i - 1][j - 1] + cost; } f[i][j] = minCost; } // System.out.println(""); } return f[aString.length()][bString.length()]; } }

在做動態規劃的題目的時候,需要明白自己需要建幾張二維表,以及二維表的實際物理意義。將原文題,遞迴到子問題。這樣可以寫出遞迴方程。
資料結構 和 遞迴方程便是程式的兩個重要部分。
如程式碼所示: f[i][j]表示 從a[i] -> b[j]的最小代價,以 “dsafsadfadf” 變成 “fdfd” 為例:
f的陣列的內容如下:

j\i f d f d
1 2 3 4
0 0 0 3 4 5 6
1 d 2 5 3 6 6
2 s 2
3 a 2
4 f 2
5 s 2
6 a 2
7 d 2
8 f 2
9 a 2
10 d 2
11 f 2

operate陣列內容如下:

j\i f d f d
1 2 3 4
0 0 ADD ADD ADD ADD
1 d DEL ADD ORIGAL ADD
2 s DEL
3 a DEL
4 f DEL
5 s DEL
6 a DEL
7 d DEL
8 f DEL
9 a DEL
10 d DEL
11 f DEL

cost=5 為一個常量,含義是由A 到B 如果兩個字元不等。那麼他所消耗的代價就為5。(刪除一個代價為2,增加一個為5) 如果字元相等,那麼他的代價就為0.
結合程式碼 以d到f 為例。有d 到f 有三種路徑可以走。

    初始條件: 由d  到0  ,消耗的代價為 2
                      由 0到 f   ,消耗的代價為 3

考慮問題的遞迴性:
1.先刪除d,那麼需要進行add f 操作,得到 f : 代價為 2+3
2.先增加f,那麼需要進行DEL d 操作,得到f: 代價為 3+2
在這兩個操作的時候考慮到遞推問題的上一個狀態,她的前一狀態是 DEL,還是ADD
3.第三種狀態就是,不管子問題,就是直接刪除一個,加一個,為5.

從左到右為 ADD狀態,從上到下為DEL狀態。ORIGAL,為對角線過來的資料,也就是說,不考慮這個字母的前一個狀態。

遞迴方程如下:
DP的遞迴方程
最右下角的值,就是我們要求得最小代價。