贴纸拼词
难度:
标签:
题目描述
代码结果
运行时间: 112 ms, 内存: 0.0 MB
/*
* 问题描述:给定若干个贴纸,每个贴纸上都有一个单词,目标是使用最少的贴纸拼出给定的目标字符串。
* 题解思路:
* 1. 统计目标字符串中每个字符的出现次数。
* 2. 使用动态规划来记录每个状态所需的最小贴纸数量。
* 3. 状态用一个整数表示,二进制中的每一位表示目标字符串中对应字符的是否已被满足。
* 4. 使用Java Stream简化遍历过程。
*/
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;
public class StickersToSpellWordStream {
public int minStickers(String[] stickers, String target) {
int n = target.length();
int[] dp = new int[1 << n];
IntStream.range(0, dp.length).forEach(i -> dp[i] = Integer.MAX_VALUE);
dp[0] = 0;
IntStream.range(0, dp.length).forEach(state -> {
if (dp[state] == Integer.MAX_VALUE) return;
for (String sticker : stickers) {
int nextState = state;
int[] count = new int[26];
sticker.chars().forEach(c -> count[c - 'a']++);
IntStream.range(0, target.length()).forEach(i -> {
char c = target.charAt(i);
if ((state >> i & 1) == 1) return; // already satisfied
if (count[c - 'a'] > 0) {
count[c - 'a']--;
nextState |= (1 << i);
}
});
dp[nextState] = Math.min(dp[nextState], dp[state] + 1);
}
});
return dp[(1 << n) - 1] == Integer.MAX_VALUE ? -1 : dp[(1 << n) - 1];
}
}
解释
方法:
这个题解采用了记忆化搜索的方法。首先对贴纸数组按照长度排序,然后用 Counter 统计每个贴纸中字母的频次。在 dfs 函数中,如果当前单词已经被记忆,直接返回记忆的结果。否则,遍历每个贴纸,如果贴纸中包含目标单词的第一个字母,则将目标单词中贴纸所包含的字母全部删去,递归处理剩余的单词。如果剩余单词为空,说明找到了一种拼接方案;如果剩余单词不为空但与原单词不同,则更新最小贴纸数量。最后将目标单词和最小贴纸数记录在备忘录中并返回结果。
时间复杂度:
O(n * m * 2^m)
空间复杂度:
O(m * 2^m)
代码细节讲解
🦆
为什么在这个题解中选择使用记忆化搜索而不是动态规划或其他方法?
▷🦆
在对贴纸数组按长度排序的基础上,这种排序是否对最终解决方案的效率有明显的提升?
▷🦆
如何理解题解中提到的`每个状态最多被访问一次`,这里的状态是指什么?
▷🦆
题解中提到,如果贴纸中不包含目标单词的第一个字母则跳过,这种做法是否可能忽略掉某些潜在的有效组合?
▷相关问题
赎金信
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。
如果可以,返回 true
;否则返回 false
。
magazine
中的每个字符只能在 ransomNote
中使用一次。
示例 1:
输入:ransomNote = "a", magazine = "b" 输出:false
示例 2:
输入:ransomNote = "aa", magazine = "ab" 输出:false
示例 3:
输入:ransomNote = "aa", magazine = "aab" 输出:true
提示:
1 <= ransomNote.length, magazine.length <= 105
ransomNote
和magazine
由小写英文字母组成