题目:
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.
思路:
确实是一道比较难的题目,我看了网上的解法之后才明白。而且感觉这种类型的DP在原来没有怎么见过。
我们定义dp[s]表示为了拼成s,所需要的sticker的最小数目。如果无法拼成,则dp[s] = -1。为了消除歧义,我们这里让s里面的字符有序排列。显然dp[""] = 0,而本题目要求的结果即为dp[target]。其中DP的递推公式为:
dp[s] = min(1 + dp[reduced_s]) for all stickers。这里reduced_s是指采用一个特定的sticker之后,s中剩余的有序字符排列。
这道DP题目并没有采用传统的计算并记录所有子问题结果的方式,而是只有需要的时候才计算,并且将计算结果保存下来,这样在下次如果遇到同样的子问题,我们就免除了重复计算,可以直接返回结果。我们将其称之为DP + memorization。
代码:
class Solution { public: int minStickers(vector<string>& stickers, string target) { int m = stickers.size(); vector<vector<int>> mp(m, vector<int>(26, 0)); // character counts for each sticker unordered_map<string, int> dp; for (int i = 0; i < m; ++i) { for (char c : stickers[i]) { ++mp[i][c-'a']; } } dp[""] = 0; return helper(dp, mp, target); } private: int helper(unordered_map<string, int>& dp, vector<vector<int>>& mp, string target) { if (dp.count(target)) { // already calculated previously, so return directly return dp[target]; } int ans = INT_MAX, n = mp.size(); vector<int> tar(26, 0); for (char c : target) { ++tar[c-'a']; } for (int i = 0; i < n; ++i) { // try every sticker if (mp[i][target[0] - 'a'] == 0) { // optimization continue; } string s; for (int j = 0; j < 26; ++j) { // apply a sticker on every character a-z if (tar[j] - mp[i][j] > 0) { s += string(tar[j] - mp[i][j], 'a' + j); } } int tmp = helper(dp, mp, s); if (tmp != -1) { ans = min(ans, 1 + tmp); } } dp[target] = ans == INT_MAX? -1 : ans; return dp[target]; } };