LeetCode691. Stickers to Spell Word

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+递归的方式来遍历。

相关文章
相关标签/搜索