在好早之前做過兩篇dp的題目總結,那個時候並沒有使用在線刷題工具,可能缺少被認證性。動態規划(Dynamic Progamming)是我最喜歡的分析方法之一,它擁有數學歸納法的優美,又可以解決計算機的問題。當然了,如果從理論角度去總結,我可能還不夠格,但是從具體的問題去總結套路,在近兩個月的刷題中也逐漸熟練。動態規划有一種,一眼看到問題一頭霧水,想通了之后豁然開朗的感覺,就像證明歸納法k=n+1時候的樣子。
借九章算法的候老師的總結,動態規划一般分為四個步驟:(1)狀態;(2)狀態轉移方程;(3)初始條件;(4)終止條件
狀態:對於一個可以迭代的問題,他的每一階的迭代就是一個狀態。比如 f(n)是一個問題為n階的解,f(n)就是一個狀態。
狀態轉移方程:狀態是一個孤立的量,而狀態之間的轉移使得動態規划是有解的。狀態轉移方程類似是:f(n)=f(n-1)+f(n-2)這種形式的;之所以動態規划可以使用狀態轉移方程,是因為它具有重疊子問題並且構造最優子結構以達到全局最優解的特性。
初始條件:對於一個有狀態的系統,它一定要有初態。
終止條件:同理。
動態規划還有一個特別明顯的特征,就是自頂向下分析問題,自底向上構造解集。
收集了一波LC的DP題目,由淺入深。
70. Climbing Stairs
這個問題很經典,非常的簡單。我們按部就班,首先找他的狀態。
(1)假設它在每一個階梯上的解集為f(n),這就是它的狀態,f(n)表示的是樓梯為n的時候,它的ways的總數。
(2)這個時候要確定它的狀態轉移,題目意思是它到達一個階梯n可以邁兩步,也可以邁一步;所以,對於f(n)來說,它有可能來自f(n-1)也有可能來自f(n-2);因此f(n)=f(n-1)+f(n-2)。
(3)初始條件,f(0)=1,因為0級樓梯只有一種方法,f(1)=1,同理。
(4)終止條件為題目的要求解。
int calculator(int n){ if(n <= 1){ return 1; }else { return calculator(n-1)+calculator(n-2); } }
這樣寫是會超時的。該為循環計算就好了。
public int climbStairs(int n) { if(n <= 1){ return 1; }else { int an_2= 1; int an_1 = 1; int an = 0; for(int i=2;i<=n;i++){ an = an_2 + an_1; an_2 = an_1; an_1 = an; } return an; } }
198. House Robber

作為一個會DP的盜賊,你得在一排聯排別墅中偷東西,並且價值最大,然后你還不能連續偷兩家,因為會觸發警報。
我們依然按部就班,從定義它的狀態開始。
(1)這里的條件是聯排別墅,那么就令f(n)表示n個別墅的時候,可以偷竊的最大價值。
(2)對於這個問題,因為它的限制是如果A[n]被搶了,那么A[n-1]必然是不能搶的。所以它的狀態轉移方程應該是f(n)=max{f(n-1),f(n-2)+A[n]},A[n]表示的是第n家的價值。
(3)初始條件f(0)=A[0],f(1)=max{A[0],A[1]}。
(4)所求。
/** * f(n) = max{f(n-1),f(n-2)+A[n]} * f(0) = A[0] * f(1) = max{f(0),A[1]} * */ public int rob1(int[] nums) { if(nums == null || nums.length == 0){ return 0; }else if(nums.length == 1){ return nums[0]; }else { int a1 = nums[0]; int a2 = Math.max(nums[1],nums[0]); int f = Math.max(a1,a2); for(int i=2;i<nums.length;i++){ f = Math.max(a1+nums[i],a2); a1 = a2; a2 = f; } return f; } }
53. Maximum Subarray
這道題是算法導論的第一個例題,也是遇到頻率非常高的題目,名曰:最大字段和。股票漲跌,算出收益最大的區間,經典問題。
這題有好幾種做法,這里只做dp的。
(1)狀態,設g(n)為長度n的數組的最大字段和,f(n)為位置n存在的最大字段,g(n)=max{f(n)}。
(2)如果一個數組位置的值加上上一個狀態,比這個位置的值還大,那么它們就是連續子段。所以一個狀態等於f(n)=max{A[n],f(n-1)+A[n]}。
(3)f(0)=0。
(4)略。
/** * 設第n個數的最大子段和為f(n) * f[n] = max{d[0],...d[n]} * d[n] = max{A[n],A[n]+d(n-1)}*/ public int maxSubArray(int[] nums) { int f = nums[0]; int max = f; for(int i=1;i<nums.length;i++){ f = Math.max(nums[i],f + nums[i]); max = Math.max(f,max); } return max; }
120. Triangle
這也是一道痕經典的數塔問題,在第n層(n>=1)有n個元素,並且是二叉樹的形式。d[n][m] 的左子樹為 d[n+1][m] ,右子樹為 d[n+1][m+1]。
這個題要從最底層不斷的合並,最后才能確定最優解。
(1)令f(i,j)表示位置為i,j的元素的最小值
(2)易得,f(i,j)=min{f(i+1,j),f(i+1,j+1)}+A[i][j]
(3)初始值,f(i,j)為最底部的A[i][j]的邊。
(4)f(0,0)為結果。
在這里我直接做了優化,使用了一維數組存儲,空間復雜度為O(n)。
class Solution { public int minimumTotal(List<List<Integer>> triangle) { if(triangle.isEmpty()){ return 0; } int[] countResult = new int[triangle.size()]; for(int i=0;i<triangle.get(triangle.size()-1).size();i++){ countResult[i]=triangle.get(triangle.size()-1).get(i); } for(int i=triangle.size()-2;i>=0;i--){ for(int j=0;j<triangle.get(i).size();j++){ countResult[j] = Math.min(triangle.get(i).get(j)+countResult[j],triangle.get(i).get(j)+countResult[j+1]); } } return countResult[0]; } }
300. Longest Increasing Subsequence
最長上升子序列,新手噩夢。subsequence的意思,是非連續元素的子集。這里要計算的是最長的上升子序列,上升的意思是大於的數在后邊。
在這里開始就不分步了,只總結關鍵的步驟。令 f(n)表示位置為n的最長字段,位置為n的意思的元素A[n]與之前的元素A[0:n-1]所組成的最長上升子序列的值,這表示題解要求的是max{f(n)}。
這個解法中,時間復雜度是O(n^2)。
class Solution { public int lengthOfLIS(int[] nums) { if(nums == null || nums.length == 0){ return 0; }else if(nums.length == 1){ return 1; } int[] record = new int[nums.length]; int len = 0; record[0] = 1; for(int i=1;i<nums.length;i++){ int cur = nums[i]; int max = 1; for(int j=i-1;j>=0;j--){ if(cur > nums[j] && record[j] + 1 > max){ max = record[j] + 1; } } record[i] = max; if(record[i] > len) len = record[i]; } return len; } }
題目中提示的O(nlogn)的解法,是維護一個最長的單調子序列,每次來一個元素就二分查找插入,最后這個容器中的size就是題解,從13ms優化到了1ms。
class Solution { public int lengthOfLIS(int[] nums) { if(nums == null){ return 0; }else if(nums.length <= 1){ return nums.length; } List<Integer> stack = new ArrayList<>(); stack.add(nums[0]); for(int i=1;i<nums.length;i++){ if(nums[i] > stack.get(stack.size()-1)) { stack.add(nums[i]); continue; } int left = 0; int right = stack.size()-1; int mid = left + (right - left)/2;; while(left < right){ if(nums[i] > stack.get(mid)){ left = mid + 1; }else if(nums[i] < stack.get(mid)){ right = mid - 1; }else { break; } mid = left + (right - left)/2; } while(true){ if(mid + 1 < stack.size() && stack.get(mid + 1) == nums[i]){ mid ++; }else { break; } } if(stack.get(mid) < nums[i]) mid ++; stack.set(mid,nums[i]); } return stack.size(); } }
152. Maximum Product Subarray
一言以蔽之,最大字段積。
這里有坑,就是負數最小值在乘以一個負數的時候,會變成最大,所以需要計算兩個軌跡,最大最小。
class Solution { public int maxProduct(int[] nums) { if(nums == null || nums.length == 0) { return 0; } int minArray = nums[0]; int maxArray = nums[0]; int result = nums[0]; for(int i=1;i<nums.length;i++){ int minI = nums[i]*minArray; int maxI = nums[i]*maxArray; minArray = Math.min(Math.min(minI,maxI),nums[i]); maxArray = Math.max(Math.max(minI,maxI),nums[i]); result = Math.max(result,maxArray); } return result; }
62. Unique Paths
這道題也是很簡單的二維動規,一個位置只能往右或者往下,求左上角到右下角的unique path。每一個位置是一個狀態,易得f(i,j)=f(i-1,j)+f(i,j-1),i>=1,j>=1。很容易優化為一維數組。
從最右下格子來看,對於每一個格子,它的來源只能是上方和左方,所以每個格子是f(i,j),他來自上方是f(i-1,j),來自左方是f(i,j-1)。
class Solution { public int uniquePaths(int m, int n) { if(m == 0 && n == 0){ return 0; }else if(n == 0 || m == 0){ return 1; } int[] calSet = new int[n]; calSet[0] = 1; for(int i=0;i<m;i++){ for(int j=1;j<n;j++){ calSet[j] += calSet[j-1]; } } return calSet[n-1]; } }
63. Unique Paths II
這道題類似於數學題第二問一樣,如果在某位置加上一個障礙,指的是任何路線經過這里都會變成0,邊界條件很坑爹。
public class UniquePathsWithObstacles { /** * f(n)(m) = f(n-1)(m)+f(n)(m-1) n>=1,m>=1 when A[n][m] == 0 * f(n)(m) = 0 A[n][m] > 0 other */ public int uniquePathsWithObstacles(int[][] obstacleGrid) { if(obstacleGrid.length == 0 && obstacleGrid[0].length == 0){ return 0; } int[] calSet = new int[obstacleGrid[0].length]; if(obstacleGrid[0][0] == 0) calSet[0] = 1; for(int i=0;i<obstacleGrid.length;i++){ for(int j=0;j<obstacleGrid[i].length;j++){ if(obstacleGrid[i][j] > 0){ calSet[j] = 0; continue; } if(j > 0){ calSet[j] += calSet[j-1]; } } } return calSet[obstacleGrid[0].length-1]; } }
322. Coin Change
找零問題,問如果有coins這些面額的硬幣,如何對amount找零使得硬幣數最小。
假設這個問題的解為f(n),它代表的是找零n所需要的最小硬幣數。f(n)=min{f(n-A[i])+1},0<=i<A.length。
對於amount=0,則需要0個硬幣,所以f(0)=0。
假設A=[1,2,5],amount=11。
f(1)=f(1-1)+1;f(1-2)+1;f(1-5)+1,那些小於0的認為是沒有解,所以略過。所以f(1)的硬幣數目是1。
f(2)=f(2-1)+1;f(2-2)+1;f(2-5)+1,這時候f(2)是最小的,所以f(2)的硬幣數目是1。
也就是說在找2塊錢最少硬幣的時候,需要對所有面額的硬幣進行找零,選出最少的一個存儲在2的位置,保證它是最優子結構。
依次迭代,最后得到amount=11的解。
class Solution { public int coinChange(int[] coins, int amount) { if(amount < 0){ return -1; } int[] dynamicProgram = new int[amount + 1]; dynamicProgram[0] = 0; for(int i=1;i<=amount;i++){ dynamicProgram[i] = Integer.MAX_VALUE; for(int coin : coins){ if(i - coin >= 0 && dynamicProgram[i-coin] < Integer.MAX_VALUE){ if(dynamicProgram[i] > dynamicProgram[i-coin] + 1){ dynamicProgram[i] = dynamicProgram[i-coin] + 1; } } } } if(dynamicProgram[amount] == Integer.MAX_VALUE){ dynamicProgram[amount] = -1; } return dynamicProgram[amount]; } }
213. House Robber II
這道搶劫題,是那道easy的升級題,它的不同是聯排別墅是環形的,也就是第一個和最后一個是相鄰的,變相的說搶第一個就不能搶最后一個了的意思。那么我們就計算兩次,搶第一個和不搶第一個,用最大價值的。
class Solution { public int rob(int[] nums) { if(nums == null || nums.length == 0){ return 0; }else if(nums.length == 1){ return nums[0]; }else { int a1 = nums[0]; int b1 = Math.max(nums[0],nums[1]); int result1 = Math.max(a1,b1); int a2 = 0; int b2 = nums[1]; int result2 = Math.max(a2,b2); for(int i=2;i<nums.length;i++){ if(i < nums.length-1){ result1 = Math.max(nums[i]+a1,b1); a1 = b1; b1 = result1; } result2 = Math.max(nums[i]+a2,b2); a2 = b2; b2 = result2; } return Math.max(result1,result2); } } }
337. House Robber III
這也是搶劫題的一個升級,就是聯排別墅是樹形的,直接連接的兩個點不能搶,這題比較抽象。
我們使用后序遍歷,每次返回一個長度為2的數組,一個數組位表示搶當前節點,一個數組位表示不搶當前節點。在遍歷完成之后,比較兩個元素取最大。這里注意,即使當前節點不搶,也要使用上級節點的兩個值中的最大值,因為涉及到最大價值。
class Solution { public int rob(TreeNode root) { int[] answer = deepFirstSearch(root); return Math.max(answer[0],answer[1]); } public int[] deepFirstSearch(TreeNode node){ if(node == null){ return new int[2]; } int[] left = deepFirstSearch(node.left); int[] right = deepFirstSearch(node.right); int[] cur = new int[2]; cur[0] = left[1] + right[1] + node.val; cur[1] = Math.max(left[0],left[1])+ Math.max(right[0],right[1]); return cur; } }
673. Number of Longest Increasing Subsequence
最大上升子序列的長度。
public class NumberOfLongestIncreasingSubsequence { public void test(){ int[] nums= {2,2,2,2,2,2}; System.out.println(findNumberOfLIS(nums)); } /** * status: let f[n] for i:0->n in A[i] the longest subsequence * let sum[n] for i:0->n in A[i] the longest subsequence counting * let j:i->n * f[j]=max{f[i]+1} if(A[i]<=A[j]) * sum[j] = sum[j] + sum[i] when f[j] = f[i] + 1 * sum[j] = sum[i] when f[j] <= f[i] * total = sum{sum[k]} if f[k] == longest */ public int findNumberOfLIS(int[] nums) { if(nums.length == 0) return 0; int[] dp = new int[nums.length]; int[] sum = new int[nums.length]; Arrays.fill(dp,1); Arrays.fill(sum,1); dp[0] = 1; int longest = 1; for(int i=1;i<nums.length;i++){ for(int j=0;j<i;j++){ if(nums[i] > nums[j]){ if(dp[i] <= dp[j]){ dp[i] = dp[j] + 1; sum[i] = sum[j]; }else if(dp[j] + 1== dp[i]){ sum[i] += sum[j]; } } } longest = Math.max(dp[i],longest); } int total = 0; for (int i = 0; i < dp.length; ++i) { if (dp[i] == longest) { total += sum[i]; } } return total; } }
10. Regular Expression Matching
這題是hard難度,最初接觸的時候毫無頭緒,甚至乎連遞歸都寫不出來。這里的難點是在於 *,這個字符,他表示0個或多個preceding字符。在沒有它的時候,這個題目就很簡單了。
public boolean isMatch2(String text, String pattern) { // when the Regular Expression is end,the text should be end if(pattern.isEmpty()) return text.isEmpty(); // match the first character boolean firstMatching = (!text.isEmpty() && text.charAt(0) == pattern.charAt(0) || pattern.charAt(0) == '.'); // current match and skip current character match return firstMatching && isMatch(text.substring(1),pattern.substring(1)); }
只考慮 . 這個字符,那么每次模式串和目標串都會遞進一個字符。而加入了*字符,那么當前可能一直匹配*,也可以跳過(零次匹配)。
public boolean isMatch(String text, String pattern) { // when the Regular Expression is end,the text should be end if(pattern.isEmpty()) return text.isEmpty(); // match the first character boolean firstMatching = (!text.isEmpty() && text.charAt(0) == pattern.charAt(0) || pattern.charAt(0) == '.'); if(pattern.length() > 1 && pattern.charAt(1) == '*'){ return isMatch(text,pattern.substring(2)) || (firstMatching && isMatch(text.substring(1),pattern)); }else { // current match and skip current character match return firstMatching && isMatch(text.substring(1),pattern.substring(1)); } }
遞歸的方法,速度比較慢,那自然有DP的解法了。令dp[i][j]代表text[0:i],pattern[0:j]的匹配狀況。
如果當前不為*,那很簡單,他們當前字符是否相等與上i-1,j-1位置的匹配就好。dp[i][j]=dp[i-1][j-1]
如果當前為*,那么它在當前去掉模式串的 X* 之后,字符是否匹配;例如 aabb 和 aab*,在最后位置的匹配應該是aabb 與 aa 的匹配基礎上 再加上 b*,因為 b* 可以代表0次。dp[i][j] = dp[i][j-2]
如果當前為*,那么*的上一個字符與當前字符相等,那么它應該與去掉目標串當前字符的匹配一致;例如 aabb 和 aab*,它的匹配度應該與 aab 和 aab* 是一致的。dp[i][j] = dp[i-1][j]
帶*的情況,只要滿足一個就算是匹配。
/** * * Dynamic Programming * let f[i,j] represent A[0:i],B[0:j] match * * if j == '*' * f[i][j]=f[i][j-2] * f[i][j]=(i==j-1 || j-1=='.')|f[i-1][j] * else if (i==j || j=='.') * f[i,j]=f[i-1,j-1] * * */ public boolean isMatch1(String text,String pattern){ boolean[][] dp = new boolean[text.length()+1][pattern.length()+1]; dp[0][0] = true; for(int i=1;i<dp[0].length;i++){ if(pattern.charAt(i-1)=='*'){ dp[0][i]=dp[0][i-2]; } } for(int i=1;i<dp.length;i++){ for(int j=1;j<dp[i].length;j++){ if(text.charAt(i-1) == pattern.charAt(j-1) || pattern.charAt(j-1) == '.'){ dp[i][j] = dp[i-1][j-1]; }else if(pattern.charAt(j-1) == '*'){ // .* repeats for zero time dp[i][j] = dp[i][j-2] ; // .* repeats for one time if(pattern.charAt(j-2) == text.charAt(i-1) || '.' == pattern.charAt(j-2)){ dp[i][j] = dp[i][j] | dp[i-1][j]; } } } } return dp[text.length()][pattern.length()]; }
32. Longest Valid Parentheses
給定一個字符串,只含有括號和反括號,確定能組成括號的最長字符串。最早,我使用stack計算最長的括號,復雜度也算是n不過有系數,大概是2n。
左括號入棧右括號出棧並且計算最大長度。這樣就得到一個數組,就是括號長度的數組,這時候從最后往前計算,加起來算出鏈接起來最長的括號字符串。
class Solution { public int longestValidParentheses(String s) { Stack<Integer> stack = new Stack<>(); int[] calculates = new int[s.length()]; int max = 0; for(int i=0;i<s.length();i++){ if(s.charAt(i) == '('){ stack.push(i); }else { if(!stack.isEmpty()){ int start = stack.pop(); calculates[i] = i - start + 1; } } } for(int i = 1;i<calculates.length;i++){ int j = i; int count = calculates[i]; while(j - calculates[j] >= 0 && calculates[j - calculates[j]] > 0){ count += calculates[j - calculates[j]]; j = j - calculates[j]; } max = Math.max(count,max); } return max; } }
對於一個長的括號字符串,它的形式可能是(),或者(()),也就是串行鏈接和嵌套。
如果是串行鏈接,那么很簡單,閉合之后為上一個位置加2。dp[i]=dp[i-1]+2。
如果是嵌套鏈接,那么其實它可能是(()),或者((...)),從右邊數過來第二個的長度加上左邊第一個加上2。dp[i] = dp[i-1] + dp[i-dp[i-1]-1] + 2。
class Solution { public int longestValidParentheses(String s) { int[] dp = new int[s.length()]; int maxVal = 0; for(int i=1;i<s.length();i++){ if(s.charAt(i) == ')' && s.charAt(i-1) == '('){ dp[i] = dp[i-1] + 2; }else if(s.charAt(i) == ')' && s.charAt(i-1) == ')'){ if(i-dp[i-1]-1 >= 0 && s.charAt(i-dp[i-1]-1) == '('){ dp[i] = dp[i-1] + dp[i-dp[i-1]-1] + 2; } } if(i-dp[i] >= 0){ dp[i] += dp[i-dp[i]]; } maxVal = Math.max(dp[i],maxVal); } return maxVal ; } }
44. Wildcard Matching
這道題其實比之前那到更為簡單,*表示一個任意長的子段。
遞歸是超時的。
public class WildcardMatching { public void test(){ // true System.out.println(isMatch("aa","a?")); // false System.out.println(isMatch("cb","?a")); // false System.out.println(isMatch("aa","a")); // true System.out.println(isMatch("aa","*")); // true System.out.println(isMatch("adceb","*a*b")); // false System.out.println(isMatch("acdcb","a*c?b")); // false System.out.println(isMatch("","?")); // true System.out.println(isMatch("","*")); // true System.out.println(isMatch("ho","ho**")); } public boolean isMatch1(String text, String pattern) { if(pattern.isEmpty()){ return text.isEmpty(); } boolean firstMatching = !text.isEmpty() && (text.charAt(0) == pattern.charAt(0)|| pattern.charAt(0) == '?'); if(pattern.charAt(0) == '*'){ if(!text.isEmpty() && pattern.length() > 1){ // repeat zero or more times return isMatch(text.substring(1),pattern) || isMatch(text,pattern.substring(1)); }else { if(!text.isEmpty()){ return isMatch(text.substring(1),pattern); }else { return isMatch(text,pattern.substring(1)); } } }else { return firstMatching && isMatch(text.substring(1),pattern.substring(1)); } } /** * * for zero or more sequence * ? for single character * * Dynamic Programming * let i for s[0:i] j for p[0:j] * f[i,j] = f[i-1,j-1] when s[i]==p[j] or p[j] == '?' * f[i,j] = f[i-1,j] or f[i,j-1] when p[j] == '*' */ public boolean isMatch(String text, String pattern){ boolean[][] dp = new boolean[text.length()+1][pattern.length()+1]; dp[0][0] = true; for(int i=1;i<dp[0].length;i++){ if(pattern.charAt(i-1) == '*') dp[0][i] = dp[0][i-1]; } for(int i=1;i<=text.length();i++){ for (int j=1;j<=pattern.length();j++){ if(pattern.charAt(j-1)=='*'){ dp[i][j] = dp[i-1][j] || dp[i][j-1]; }else { dp[i][j] = (text.charAt(i-1) == pattern.charAt(j-1) || pattern.charAt(j-1) == '?') && dp[i-1][j-1]; } } } return dp[text.length()][pattern.length()]; } }
97. Interleaving String
s1與s2交錯字符串成s3。
public class InterleavingString { public void test(){ // true System.out.println(isInterleave("ab","ba","abba")); // true System.out.println(isInterleave("aabcc","dbbca","aadbbcbcac")); // false System.out.println(isInterleave("aabcc","dbbca","aadbbbaccc")); // false System.out.println(isInterleave("","","a")); // true System.out.println(isInterleave("","","")); // false System.out.println(isInterleave("a","","aa")); // true System.out.println(isInterleave("aabc","abad","aabadabc")); } /** * s1,s2 construct s3 * * i for s1[0:i],j for s2[0:j] * i+j for s3 * dp[i][j] = (dp[i-1][j] when s3(i+j-1)==s1(i-1)) or (dp[i][j-1] when s3(i+j-1)==s2(j-1)) */ public boolean isInterleave(String s1, String s2, String s3) { if (s3.length() != s1.length() + s2.length()) { return false; } boolean[][] dp = new boolean[s1.length()+1][s2.length()+1]; dp[0][0] = true; for(int i=1;i<=s2.length();i++){ if(s3.charAt(i-1) == s2.charAt(i-1)){ dp[0][i] = dp[0][i-1]; } } for(int i=1;i<=s1.length();i++){ if(s3.charAt(i-1) == s1.charAt(i-1)){ dp[i][0] = dp[i-1][0]; } } for(int i=1;i<=s1.length();i++){ for(int j=1;j<=s2.length();j++){ if(s3.charAt(i+j-1) == s1.charAt(i-1)){ dp[i][j] = dp[i-1][j]; } if(s3.charAt(i+j-1) == s2.charAt(j-1)){ dp[i][j] |= dp[i][j-1]; } } } return dp[s1.length()][s2.length()]; } }
639. Decode Ways II
解碼游戲,*表示任意數字。
class Solution { /** * 'A' -> 1 * 'B' -> 2 * ... * 'Z' -> 26 * * '*' for '1'-'9' * * .....'*'1 * .....1'*' * * Dynamic Programming * let f[i] for s[0:i] type sum * * s[0] = 1 * if s[i] == '1'->'9' * s[i] += s[i-1] * if s[i-1:i] == '10'->'26' * s[i] += s[i-2] * * if s[i] == '*' * s[i] += 9*s[i-1] * if s[i-1] == '1' and s[i] == '*' * s[i] += 9*s[i-2] * if s[i-1] == '2' and s[i] == '*' * s[i] += 6*s[i-2] * * if s[i-1] == '*' and s[i] == '0'->'6' * s[i] += 2*s[i-2] * if s[i-1] == '*' and s[i] == '7'->'9' * s[i] += s[i-2] * * // total type for this is 11-19 and 21-26 * if s[i-1] == '*' and s[i] == '*' * s[i] += 15*s[i-2] */ public int numDecodings(String s) { if(s == null || s.length() == 0){ return 0; } // fuck , why not return long // gao zheme duo yaoerzi // since the answer is very large you should return the output mod 109 + 7. int M = 1000000007; long[] dp = new long[s.length()+1]; dp[0] = 1; dp[1] = s.charAt(0) == '*' ? 9 : s.charAt(0) == '0' ? 0 : 1; for(int i=2;i<=s.length();i++){ char charactor = s.charAt(i-1); char charactorPre = s.charAt(i-2); if(charactor != '*' && charactorPre != '*'){ if(charactor >= '1' && charactor <= '9'){ dp[i] = (dp[i] + dp[i-1]) % M; } int val = Integer.parseInt(s.substring(i-2,i)); if(val >= 10 && val <= 26){ dp[i] = (dp[i] + dp[i-2]) % M; } }else if(charactorPre != '*'){ dp[i] = (dp[i] + 9*dp[i-1]) % M; if(charactorPre == '1'){ dp[i] = (dp[i] + 9*dp[i-2]) % M; }else if(charactorPre == '2'){ dp[i] = (dp[i] + 6*dp[i-2]) % M; } }else if(charactor != '*'){ if(charactor >= '1' && charactor <= '9'){ dp[i] = (dp[i] + dp[i-1]) % M; } if(charactor >= '0' && charactor <= '6'){ dp[i] = (dp[i] + 2*dp[i-2]) % M; } if(charactor >= '7' && charactor <= '9'){ dp[i] = (dp[i] + dp[i-2]) % M; } }else { dp[i] = (dp[i] + 9*dp[i-1]+15*dp[i-2]) % M; } } return (int)dp[s.length()]; } }