一、题目
我们有 n 种不同的贴纸。每个贴纸上都有一个小写的英文单词。
您想要拼写出给定的字符串 target ,方法是从收集的贴纸中切割单个字母并重新排列它们。如果你愿意,你可以多次使用每个贴纸,每个贴纸的数量是无限的。
返回你需要拼出 target 的最小贴纸数量。如果任务不可能,则返回 -1 。
OJ链接
二、题解
2.1 方法一:递归尝试
递归函数:
stickers :表示已有的贴纸
target:目标字符串
终止条件:
当target.length==0时,意味着目标字符串长度为0,那么直接返回0张贴纸
先假设每张贴张都作为第一张贴纸,使用minus函数剪掉target中的和first中的共有的字符,得到的rest作为下一次调用的target
如果rest的长度和target的长度相同,说明target中和当前的first中不存在重复的字符,就去尝试下一张贴纸作为第一张提位置的情况
返回需要的最少的贴纸时,如果是有效的解,需要加上使用的第一张贴纸
源码:
👿
public static int minStickers1(String[] stickers, String target) {
if(target==null||target.length()==0){
return 0;
}
int ans=process1(stickers,target);
return ans==Integer.MAX_VALUE?-1:ans;
}
public static int process1(String[] stickers,String target){
if(target.length()==0){
return 0;
}
int min=Integer.MAX_VALUE;
for (String first:stickers) {
String rest=minus(target,first);
if(target.length()!=rest.length()){
min=Math.min(min,process1(stickers,rest));
}
}
return min+(min==Integer.MAX_VALUE?0:1);
}
public static String minus(String s1,String s2){
char[] str1=s1.toCharArray();
char[] str2=s2.toCharArray();
int[] count=new int[26];
for (char ch:str1) {
count[ch-'a']++;
}
for (char ch:str2) {
count[ch-'a']--;
}
StringBuilder sb=new StringBuilder();
for (int i = 0; i <count.length ; i++) {
if(count[i]>0){
while(count[i]>0){
sb.append(i+'a');
count[i]--;
}
}
}
return sb.toString();
}
2.2方法二:剪枝
思路:
- 用一个二维数组来存储所有的贴纸
- 在方法一中,依次使用每一张贴纸都作为第一张贴纸去尝试,该方法的优化是选所有贴纸中具有目标贴纸的第一个字符的贴纸作为第一张贴纸去往下尝试,因为对于目标贴纸中的第一个字符在后续中总是要消掉的,放在前面先消掉,不会对结果有影响。(这样就实现了剪枝)
源码:
😇
public static int process2(int[][] stickers,String target){
if(target.length()==0) {
return 0;
}
char[] t=target.toCharArray();
int[] tcounts=new int[26];
for (char ch:t) {
tcounts[ch-'a']++;
}
int N=stickers.length;
int min=Integer.MAX_VALUE;
for (int i = 0; i < N; i++) {
int[] sticker=stickers[i];
//剪枝
if(sticker[t[0]-'a']>0){
StringBuilder builder=new StringBuilder();
for (int j = 0; j <26 ; j++) {
if(tcounts[j]>0){
int nums=tcounts[j]-sticker[j];
for (int k = 0; k < nums; k++) {
builder.append((char)(j+'a'));
}
}
}
String rest=builder.toString();
min=Math.min(min,process2(stickers,rest));
}
}
return min+(min==Integer.MAX_VALUE?0:1);
}
public static int minStickers2(String[] stickers, String target) {
int N=stickers.length;
int[][] counts=new int[N][26];
//生成所有贴纸的词频统计
for (int i = 0; i < N; i++) {
char[] str=stickers[i].toCharArray();
for (char ch:str) {
counts[i][ch-'a']++;
}
}
int ans=process2(counts,target);
return ans==Integer.MAX_VALUE?-1:ans;
}
2.3 方法三:缓存表
在方法二的基础上多添加了一个缓存表
源码:
😐
public static int process3(int[][] stickers, String target, HashMap<String,Integer> dp) {
if(dp.containsKey(target)){
return dp.get(target);
}
if (target.length() == 0) {
return 0;
}
char[] t=target.toCharArray();
int[] tcounts=new int[26];
for (char ch:t) {
tcounts[ch-'a']++;
}
int N=stickers.length;
int min=Integer.MAX_VALUE;
for (int i = 0; i < N; i++) {
int[] sticker=stickers[i];
//剪枝
if(sticker[t[0]-'a']>0){
StringBuilder builder=new StringBuilder();
for (int j = 0; j <26 ; j++) {
if(tcounts[j]>0){
int nums=tcounts[j]-sticker[j];
for (int k = 0; k < nums; k++) {
builder.append((char)(j+'a'));
}
}
}
String rest=builder.toString();
min=Math.min(min,process3(stickers,rest,dp));
}
}
int ans=min+(min==Integer.MAX_VALUE?0:1);
dp.put(target,ans);
return ans;
}
public static int minStickers3(String[] stickers, String target) {
int N=stickers.length;
int[][] counts=new int[N][26];
//生成所有贴纸的词频统计
for (int i = 0; i < N; i++) {
char[] str=stickers[i].toCharArray();
for (char ch:str) {
counts[i][ch-'a']++;
}
}
HashMap<String,Integer> dp=new HashMap<>();
dp.put("",0);
int ans=process3(counts,target,dp);
return ans==Integer.MAX_VALUE?-1:ans;
}