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.

Note:

  • stickers has length in the range [1, 50].
  • stickers consists of lowercase English words (without apostrophes).
  • target has length in the range [1, 15], and consists of lowercase English letters.
  • In all test cases, all words were chosen randomly from the 1000 most common US English words, and the target was chosen as a concatenation of two random words.
  • The time limit may be more challenging than usual. It is expected that a 50 sticker test case can be solved within 35ms on average.

这道题给了我们N个贴片,每个贴片上有一个小写字母的单词,给了我们一个目标单词target,让我们通过剪下贴片单词上的字母来拼出目标值,每个贴片都有无数个,问我们最少用几个贴片能拼出目标值target,如果不能拼出来的话,就返回-1。这道题博主最开始尝试用贪婪算法,结果发现不行,有网友留言提示说是多重背包问题,然后去论坛上看大神们的解法,果然都是用DP做的,之前曾有网友推荐过一个“背包九讲”的帖子,大概扫过几眼,真是叼到飞起啊,博主希望有时间也能总结一下。先来看这道题吧,既然是用DP来做,那么就需要用dp数组了,我们使用一个一维的dp数组,其中dp[i]表示组成第i个子集合所需要的最少的sticker的个数,注意这里是子集合,而不是子串。长度为n的字符串共有2的n次方个子集合,比如字符串"ab",就有4个子集合,分别是 "", "a", "b", "ab"。字符串"abc"就有8个子集合,如果我们用0到7来分别对应其子集合,就有:

     abc   subset
""
0 c
b
0 bc
1 a
ac
1 bb
abc

我们可以看到0到7的二进制数的每一位上的0和1就相当于mask,是1的话就选上该位对应的字母,000就表示都不选,就是空集,111就表示都选,就是abc,那么只要我们将所有子集合的dp值都算出来,最后返回dp数组的最后一个位置上的数字,就是和目标子串相等的子集合啦。我们以下面这个简单的例子来讲解:

stickers = ["ab", "bc", "ac"], target = "abc"

之前说了abc的共有8个子集合,所以dp数组的长度为8,除了dp[0]初始化为0之外,其余的都初始化为INT_MAX,然后我们开始逐个更新dp数组的值,我们的目标是从sticker中取出字符,来拼出子集合,所以如果当前遍历到的dp值仍为INT_MAX的话,说明该子集合无法被拼出来,自然我们也无法再其基础上去拼别打子集合了,直接跳过。否则我们就来遍历所有的sticker,让变量cur等于i,说明此时是在第i个子集合的基础上去reach其他的子集合,我们遍历当前sticker的每一个字母,对于sticker的每个字母,我们都扫描一遍target的所有字符,如果target里有这个字符,且该字符未出现在当前cur位置的子集合中,则将该字符加入子集合中。什么意思呢,比如当前我们的cur是3,二进制为011,对应的子集合是"bc",此时如果我们遍历到的sticker是"ab",那么遇到"a"时,我们知道target中是有"a"的,然后我们看"bc"中包不包括"a",判断方法是看 (cur >> k) & 1 是否为0,这可以乍看上去不太好理解,其实不难想,因为我们的子集合是跟二进制对应的,"bc"就对应着011,第一个0就表示"a"缺失,所以我们想看哪个字符,就提取出该字符对应的二进制位,提取方法就是 cur >> k,表示cur向右移动k位,懂位操作Bit Manipulation的童鞋应该不难理解,提出出来的值与上1就知道该位是0还是1了,如果是0,表示缺失,那么把该位变为1,通过 cur |= 1 << k来实现,那么此时我们的cur就变位7,二进制为111,对应的子集合是"abc",更新此时的dp[cur]为 dp[cur] 和 dp[i] + 1 中的较大值即可,最后循环结束后,如果"abc"对应的dp值还是INT_MAX,就返回-1,否则返回其dp值,参见代码如下:

解法一:

class Solution {
public:
int minStickers(vector<string>& stickers, string target) {
int n = target.size(), m = << n;
vector<int> dp(m, INT_MAX);
dp[] = ;
for (int i = ; i < m; ++i) {
if (dp[i] == INT_MAX) continue;
for (string &sticker : stickers) {
int cur = i;
for (char c : sticker) {
for (int k = ; k < n; ++k) {
if (target[k] == c && !((cur >> k) & )) {
cur |= << k;
break;
}
}
}
dp[cur] = min(dp[cur], dp[i] + );
}
}
return dp[m - ] == INT_MAX ? - : dp[m - ];
}
};

下面这种解法是带记忆数组memo的递归解法,可以看作是DP解法的递归形式,核心思想都一样。只不过dp数组换成了哈希Map,建立子集合跟最小使用的sticker的个数之间的映射,初始化空集为0,我们首先统计每个sticker的各个字母出现的频率,放到对应的二维数组freq中,然后就是调用递归函数。在递归函数中,首先判断,如果target已经在memo中,直接返回其值。否则我们开始计算,首先统计出此时的target字符串的各个字母出现次数,然后我们遍历统计所有sticker中各个字母出现次数的数组freq,如果target字符串的第一个字母不在当前sticker中,我们直接跳过,注意递归函数中的target字符串不是原始的字符串,我们心间一个临时字符串t,然后我们遍历target字符串中存在的字符,如果target中的某字符存在的个数多于sticker中对应的字符,那么将多余的部分存在字符串t中,表示当前sticker无法拼出的字符,交给下一个递归函数来处理,我们看再次调用递归函数的结果ans,如果不为-1,说明可以拼出剩余的那些字符,那么此时我们的res更新为ans+1,循环退出后,此时我们的res就应该是组成当前递归函数中的target串的最少贴片数,更新dp[target]值,如果res是INT_MAX,说明无法拼出,更新为-1,否则更新为res,参见代码如下:

解法二:

class Solution {
public:
int minStickers(vector<string>& stickers, string target) {
int N = stickers.size();
vector<vector<int>> freq(N, vector<int>(, ));
unordered_map<string, int> memo;
memo[""] = ;
for (int i = ; i < N; ++i) {
for (char c : stickers[i]) ++freq[i][c - 'a'];
}
return helper(freq, target, memo);
}
int helper(vector<vector<int>>& freq, string target, unordered_map<string, int>& memo) {
if (memo.count(target)) return memo[target];
int res = INT_MAX, N = freq.size();
vector<int> cnt(, );
for (char c : target) ++cnt[c - 'a'];
for (int i = ; i < N; ++i) {
if (freq[i][target[] - 'a'] == ) continue;
string t = "";
for (int j = ; j < ; ++j) {
if (cnt[j] - freq[i][j] > ) t += string(cnt[j] - freq[i][j], 'a' + j);
}
int ans = helper(freq, t, memo);
if (ans != -) res = min(res, ans + );
}
memo[target] = (res == INT_MAX) ? - : res;
return memo[target];
}
};

类似题目:

Ransom Note

参考资料:

https://leetcode.com/problems/stickers-to-spell-word/discuss/108333/Rewrite-of-contest-winner's-solution

https://leetcode.com/problems/stickers-to-spell-word/discuss/108318/C++JavaPython-DP-+-Memoization-with-optimization-29-ms-(C++)

https://leetcode.com/problems/stickers-to-spell-word/discuss/108334/Explaining-StefanPochmann's-Rewrite-of-contest-winner's-solution-and-+java

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

[LeetCode] 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 691. Stickers to Spell Word

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

  3. LeetCode691. Stickers to Spell Word

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

  4. 691. Stickers to Spell Word

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

  5. [LeetCode] Length of Last Word 求末尾单词的长度

    Given a string s consists of upper/lower-case alphabets and empty space characters ' ', return the l ...

  6. [Leetcode] Length of last word 最后一个单词的长度

    Given a string s consists of upper/lower-case alphabets and empty space characters' ', return the le ...

  7. [LeetCode] Valid Word Square 验证单词平方

    Given a sequence of words, check whether it forms a valid word square. A sequence of words forms a v ...

  8. [LeetCode] Valid Word Abbreviation 验证单词缩写

    Given a non-empty string s and an abbreviation abbr, return whether the string matches with the give ...

  9. [LeetCode] Maximum Product of Word Lengths 单词长度的最大积

    Given a string array words, find the maximum value of length(word[i]) * length(word[j]) where the tw ...

随机推荐

  1. PHP 相对完整的分页

    效果链接http://love.bjxxw.com/oejiaoyou/pubu/zhaopian.php php 分页 <?php /* * * * 说明 吉海波 2015/9/17 * $p ...

  2. Notepad++使用vs2015主题教程

    前言: 最近几天都在用Notepad++,所以想换个看得舒服点的主题. 发现vs2015的主题颜色特别好看.所以就查了一下有没有大佬做了这样的Notepad++主题. 结果是有的. 正文: notep ...

  3. C语言程序设计(基础)- 第7周作业(新)

    要求一(25经验值) 完成PTA中题目集名为<usth-C语言基础-第七周作业>和<usth-C语言基础-12周PTA作业>中的所有题目. 注意1:<usth-C语言基础 ...

  4. [高级软件工程教学]团队Beta阶段成绩汇总

    一.作业地址: https://edu.cnblogs.com/campus/fzu/AdvancedSoftwareEngineering/homework/1501 二.Beta冲刺课堂答辩 1. ...

  5. lambda及参数绑定

    一.介绍   对于STL中的算法,我们都可以传递任何类别的可调用对象.对于一个对象或一个表达式,如果可以对其使用调用运算符,则称它为可调用的.即,如果e是一个可调用的表达式,则我们可以编写代码e(ar ...

  6. cpp常用函数总结

    //sprintf sprintf(temp_str_result, "%lf", temp_double); result = temp_str_result; (*begin) ...

  7. AWS中的Internet 网关

    nternet 网关是一种横向扩展.支持冗余且高度可用的 VPC 组件,可实现 VPC 中的实例与 Internet 之间的通信.因此它不会对网络流量造成可用性风险或带宽限制. Internet 网关 ...

  8. Mysql数据库的触发程序

    /** **创建表 */ CREATE TABLE test1(a1 INT); CREATE TABLE test2(a2 INT); CREATE TABLE test3(a3 INT NOT N ...

  9. 【iOS】跳转到设置页面

    iOS8.0以后有效 定位服务 定位服务有很多APP都有,如果用户关闭了定位,那么,我们在APP里面可以提示用户打开定位服务.点击到设置界面设置,直接跳到定位服务设置界面.代码如下: 1 2 3 4 ...

  10. Mysql必须知道的知识

    最近在准备面试,所以也整理了一些Mysql数据库常用的知识,供大家参考. 1.MySQL的复制原理以及流程 (1).复制基本原理流程 1. 主:binlog线程--记录下所有改变了数据库数据的语句,放 ...