We are given N different types of stickers. Each sticker has a lowercase English word on it.數組
You would like to spell out the given target
string by cutting individual letters from your collection of stickers and rearranging them.app
You can use each sticker more than once if you want, and you have infinite quantities of each sticker.優化
What is the minimum number of stickers that you need to spell out the target
? If the task is impossible, return -1.ui
Example 1:this
Input:spa
["with", "example", "science"], "thehat"
Output:code
3
Explanation:orm
We can use 2 "with" stickers, and 1 "example" sticker.
After cutting and rearrange the letters of those stickers, we can form the target "thehat".
Also, this is the minimum number of stickers necessary to form the target string.
Example 2:blog
Input:遞歸
["notice", "possible"], "basicbasic"
Output:
-1
Explanation:
We can't form the target "basicbasic" from cutting letters from the given stickers.
分析
乍一看應該是個dp問題,可是不知道怎麼建模,仍是多作題目來積累經驗吧。
首先是要明白題目的意思,從繞來繞去的描述中尋找題目真正要求的是什麼。這題的輸入是一組字符串,和一個目標字符串,對於給定的字符串能夠取它任意的分割部分,同一個字符串可使用屢次,以此來組成目標字符串,求從輸入的那組字符串中取最少的字符串個樹來組成目標字符串。很繁瑣的描述,剝離下無用信息其實就是從給定一個字符串集合S,以及一個目標字符串T.求使用S中字符串的最小個數,可以知足T須要的字符數。
利用數組下標與值的映射關係來存儲每一個sticker以及target string中的字符及其數目,而且使用回溯來遍歷全部的可能。
看了下遞歸遍歷的代碼,仍是不是很懂,仍是去看了下dp的解法。利用dp加上回溯遞歸的方法遍歷求解全部的可能。對於每個sticker,應用到組成target string的話,組成了一部分,還剩下了一部分,這樣遞歸求解便可。
利用一個map存儲dp數組,key是要組成的target string,值是最優解,也就最少使用多少個sticker。
dp[s] is the minimum stickers required for string s (-1 if impossible). Note s is sorted. clearly, dp[""] = 0, and the problem asks for dp[target].
狀態轉移方程:
dp[s] = min(1+dp[reduced_s]) for all stickers, here reduced_s is a new string after certain sticker applied
上面的意思是對於s咱們循環從全部stickers中選則一個來組成它的一部分,那麼它剩下的部分還要繼續組成,這就成了相同的問題。遞歸求解便可,多說無益直接上代碼來分析:
class Solution { public int minStickers(String[] stickers, String target) { int m = stickers.length; int[][] mp = new int[m][26]; Map<String, Integer> dp = new HashMap<>(); for (int i = 0; i < m; i++) for (char c:stickers[i].toCharArray()) mp[i][c-'a']++; // m行表明m個sticker,0~25列表明字母a~z,實際上將字符串中的字符按順序排列了
dp.put("", 0); return helper(dp, mp, target); } // mp數組存儲每一個sticker的字符及其數量,target是當前遞歸層次中要組成的目標字符串
private int helper(Map<String, Integer> dp, int[][] mp, String target) { if (dp.containsKey(target)) return dp.get(target); int ans = Integer.MAX_VALUE, n = mp.length; int[] tar = new int[26]; for (char c:target.toCharArray()) tar[c-'a']++; // 存儲組成目標字符串所須要的字符及其數量 // 對於每一個sticker嘗試將其做爲target string的組成部分,遞歸求解
for (int i = 0; i < n; i++) { // 這裏的優化頗有意思,使得整個循環從包含target string中的第一個字符的sticker開始,這樣會減小計算
if (mp[i][target.charAt(0)-'a'] == 0) continue; StringBuilder sb = new StringBuilder(); // 用來存儲剩下要組成的target字符串,也就是參與下一輪遞歸的部分 // 對於當前的sticker,從a~z匹配
for (int j = 0; j < 26; j++) { // 若是target string須要某個數量的字符而當前sticker中有必定數量的這個字符,將須要的數量減去已有的數量即是剩下還須要的這個字符的數量,在下輪遞歸中繼續尋找
if (tar[j] > 0 ) for (int k = 0; k < Math.max(0, tar[j]-mp[i][j]); k++) sb.append((char)('a'+j)); } String s = sb.toString(); int tmp = helper(dp, mp, s); // 將剩下要組成的部分做爲新的target string傳給下層迭代,由於sticker能夠重複利用因此mp不動
if (tmp != -1) ans = Math.min(ans, 1+tmp); } dp.put(target, ans == Integer.MAX_VALUE? -1:ans); return dp.get(target); } }
上面那個優化是頗有意思的,由於若是target string能由全部stickers拼出來的話,那麼包含了target中的第一個字符的全部sticker至少有一個是在最後的結果中的,那麼反過來想,咱們在由stickers去遍歷全部可能的時候能夠直接從包含了target string中第一個字符的sticker出發。這題仍是比較難的,首先用map來存儲了dp數組,其次是用到了dp+遞歸的方式來遍歷。