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.

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.

Example 1:

Input:

["with", "example", "science"], "thehat"

Output:

3

Explanation:

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:

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

LeetCode691. Stickers to Spell Word的更多相关文章

  1. [Swift]LeetCode691. 贴纸拼词 | Stickers to Spell Word

    We are given N different types of stickers. Each sticker has a lowercase English word on it. You wou ...

  2. [LeetCode] Stickers to Spell Word 贴片拼单词

    We are given N different types of stickers. Each sticker has a lowercase English word on it. You wou ...

  3. 691. Stickers to Spell Word

    We are given N different types of stickers. Each sticker has a lowercase English word on it. You wou ...

  4. LeetCode 691. Stickers to Spell Word

    原题链接在这里:https://leetcode.com/problems/stickers-to-spell-word/ 题目: We are given N different types of ...

  5. leetcode bugfree note

    463. Island Perimeterhttps://leetcode.com/problems/island-perimeter/就是逐一遍历所有的cell,用分离的cell总的的边数减去重叠的 ...

  6. Swift LeetCode 目录 | Catalog

    请点击页面左上角 -> Fork me on Github 或直接访问本项目Github地址:LeetCode Solution by Swift    说明:题目中含有$符号则为付费题目. 如 ...

  7. LeetCode All in One题解汇总(持续更新中...)

    突然很想刷刷题,LeetCode是一个不错的选择,忽略了输入输出,更好的突出了算法,省去了不少时间. dalao们发现了任何错误,或是代码无法通过,或是有更好的解法,或是有任何疑问和建议的话,可以在对 ...

  8. leetcode 学习心得 (4)

    645. Set Mismatch The set S originally contains numbers from 1 to n. But unfortunately, due to the d ...

  9. All LeetCode Questions List 题目汇总

    All LeetCode Questions List(Part of Answers, still updating) 题目汇总及部分答案(持续更新中) Leetcode problems clas ...

随机推荐

  1. 【DP】【P4539】 [SCOI2006]zh_tree

    Description 张老师根据自己工作的需要,设计了一种特殊的二叉搜索树. 他把这种二叉树起名为zh_tree,对于具有n个结点的zh_tree,其中序遍历恰好为(1,2,3,-,n),其中数字1 ...

  2. 4:JAVA UUID 生成

    GUID是一个128位长的数字,一般用16进制表示.算法的核心思想是结合机器的网卡.当地时间.一个随即数来生成GUID.从理论上讲,如果一台机器每秒产生10000000个GUID,则可以保证(概率意义 ...

  3. MVC 中@Html.DropDownListFor() 设置选中项 这么不好使 ? [问题点数:40分,结帖人lkf181]

    http://bbs.csdn.net/topics/390867060 由于不知道错误原因在哪 我尽量把代码都贴出来吧:重点是:在 Controller 类里 我给 SelectListItem集合 ...

  4. 【题解】打地鼠 SDOI2011 模拟 行列无关

    Prelude 为什么洛谷上的题解都是剪枝做的啊!就没有人写复杂度靠谱的算法吗! 传送到洛谷:( ̄. ̄) 传送到BZOJ:( ´・・)ノ(._.`) 本篇博客地址:o(><:)oo Sol ...

  5. django中的认证与登录

    认证登录 django.contrib.auth中提供了许多方法,这里主要介绍其中的三个: 1  authenticate(**credentials)    提供了用户认证,即验证用户名以及密码是否 ...

  6. 用canvas绘制验证码

    在通常的登录界面我们都可以看到验证码,验证码的作用是检测是不是人在操作,防止机器等非人操作,防止数据库被轻而易举的攻破. 验证码一般用PHP和java等后端语言编写: 但是在前端,用canva或者SV ...

  7. 贪心问题 POJ 2393 Yogurt factory

    题目:http://poj.org/problem?id=2393 题意:N周,每周生成牛奶(任意!),每周成本为c_i(1~5000),每周出货 y_i:出货可以使用该周生产的,也可以用之前的储存的 ...

  8. RTSP服务器之————rtsp-server(轻量级RTSP / RTP流媒体服务器)

    github:https://github.com/revmischa/rtsp-server 轻量级RTSP / RTP流媒体服务器

  9. Django 2.0.1 官方文档翻译:接下来读什么(page 14)

    接下来读什么(page 14) 现在你应该已经阅读了所有的(page1-13 )介绍材料,决定继续使用Django.我们仅仅做了简要的介绍(事实上,如果你阅读了前面所有的内容,也只是全部文档的5%.) ...

  10. jQuery 动态标签生成插件

    前言: 最近对js的插件封装特别感兴趣,无耐就目前的技术想做到js的完全封装,还是有一定困难,就基于jQuery封装了一个小的插件,而且是基于对象级开发的,不是添加全局方法.高深的语法几乎没有,就有一 ...