创意标题匹配
题目描述
在广告平台中,为了给广告主一定的自由性和效率,允许广告主在创造标题的时候以通配符的方式进行创意提交。线上服务的时候,会根据用户的搜索词触发的 bidword 对创意中的通配符(通配符是用成对 {} 括起来的字符串,可以包含 0 个或者多个字符)进行替换,用来提升广告投放体验。例如:“{末日血战} 上线送 SSR 英雄,三天集齐无敌阵容!”,会被替换成“帝国时代游戏下载上线送 SSR 英雄,三天集齐无敌阵容!”。给定一个含有通配符的创意和n个标题,判断这句标题是否从该创意替换生成的。
测试样例
样例1:
输入:
n = 4, template = "ad{xyz}cdc{y}f{x}e", titles = ["adcdcefdfeffe", "adcdcefdfeff", "dcdcefdfeffe", "adcdcfe"]
输出:"True,False,False,True"
样例2:
输入:
n = 3, template = "a{bdc}efg", titles = ["abcdefg", "abefg", "efg"]
输出:"True,True,False"
问题理解
给定一个模板字符串template,其中由固定部分和通配符"{}"组成。我们要做的就是将titles数组中的每个标题对应匹配template的组成部分。其中通配符可以代表0个字符或多个字符。
自己的思路分析
刚看到题目时,被这道题搞懵了,平时做的字符串模式匹配想到了kmp,但这道题又有些变幻。我认为难点在:
- 由于通配符是可以为0的字符,如果用双指针做匹配的话,难以进行调整。比如:样例1的匹配到"ad"的字符成功,但此时template是"{xyz}",而titles[0]的标题此时匹配到"cdc"是在template为固定的部分。此时假如匹配到通配符就跳过就错误了。
- 由于我是用java做的,还得从头开始写对模板的解析。
正确的解题思路
- 模板解析:我们需要将模板解析成固定部分和通配符部分。可以使用正则表达式来识别通配符。
- 匹配逻辑:对于每个标题,我们需要尝试将其与模板进行匹配。匹配过程中,我们需要确保标题中的字符能够按照模板的结构进行分配。
代码写法
- 我们先将模板
template分为parts数组,如["a","{bdc},"efg"],将固定部分和通配分隔。 - 我们对每个
parts的每个部分进行匹配。 - 如
title="abefg",此时先从parts[0]="a"开始匹配,我们运用迭代,设置此时的title的匹配的位置tIndex和parts的匹配的部分...pIndex匹配流程如下:
-
- 当
pIndex==parts.size(),返回tIndex==title.length()->代表是否匹配成功
- 当
-
- 如果匹配到固定部分,则进行part和title部分的匹配->成功,继续跳到title的tIndex+part[0].length()进行匹配,失败返回false
-
- 如果匹配到通配符,需要尝试匹配所有可能的任意长度(相当于跳过i个字符)
for (int i = tIndex; i <= title.length(); i++)->此时对于每个结果匹配,当tIndex=i,pIndex=pIndex+1,又回到2的过程.
- 如果匹配到通配符,需要尝试匹配所有可能的任意长度(相当于跳过i个字符)
-
- 最后返回true或者false.
##代码实现
public class Main {
// 定义一个类来表示创意的每一部分
static class Part {
String value;
boolean isWildcard;
Part(String value, boolean isWildcard) {
this.value = value;
this.isWildcard = isWildcard;
}
}
public static List<Part> parseCreative(String creative) {
List<Part> parts = new ArrayList<>();
StringBuilder sb = new StringBuilder();
boolean inWildcard = false;
for (int i = 0; i < creative.length(); i++) {
char c = creative.charAt(i);
if (c == '{') {
// 如果有积累的固定部分,添加到列表
if (sb.length() > 0) {
parts.add(new Part(sb.toString(), false));
sb.setLength(0);
}
inWildcard = true;
} else if (c == '}') {
// 通配符结束,添加到列表
parts.add(new Part(sb.toString(), true));
sb.setLength(0);
inWildcard = false;
} else {
sb.append(c);
}
}
// 添加剩余的固定部分
if (sb.length() > 0) {
parts.add(new Part(sb.toString(), false));
}
return parts;
}
private static boolean matchHelper(String title, int tIndex, List<Part> parts, int pIndex) {
// 如果所有部分都匹配完毕,检查标题是否也完全匹配
if (pIndex == parts.size()) {
return tIndex == title.length();
}
Part currentPart = parts.get(pIndex);
if (!currentPart.isWildcard) {
// 固定部分需要精确匹配
if (title.startsWith(currentPart.value, tIndex)) {
return matchHelper(title, tIndex + currentPart.value.length(), parts, pIndex + 1);
} else {
return false;
}
} else {
// 通配符部分,可以匹配任意长度,包括空字符串
// 尝试所有可能的匹配长度
for (int i = tIndex; i <= title.length(); i++) {
if (matchHelper(title, i, parts, pIndex + 1)) {
return true;
}
}
return false;
}
}
public static String solution(int n, String template, String[] titles) {
List<Part> parts = parseCreative(template);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
if (matchHelper(titles[i], 0, parts, 0)) {
sb.append("True");
} else {
sb.append("False");
}
if (i == n - 1) {
break;
}
sb.append(",");
}
// System.out.println(sb.toString());
return sb.toString();
}
public static void main(String[] args) {
// You can add more test cases here
String[] testTitles1 = { "adcdcefdfeffe", "adcdcefdfeff", "dcdcefdfeffe", "adcdcfe" };
String[] testTitles2 = { "CLSomGhcQNvFuzENTAMLCqxBdj", "CLSomNvFuXTASzENTAMLCqxBdj", "CLSomFuXTASzExBdj",
"CLSoQNvFuMLCqxBdj", "SovFuXTASzENTAMLCq", "mGhcQNvFuXTASzENTAMLCqx" };
String[] testTitles3 = { "abcdefg", "abefg", "efg" };
// System.out.println(solution(4, "ad{xyz}cdc{y}f{x}e",
// testTitles1).equals("True,False,Fales,True"));
System.out.println(solution(4, "ad{xyz}cdc{y}f{x}e", testTitles1).equals("True,False,False,True"));
// System.out.println(solution(6, "{xxx}h{cQ}N{vF}u{XTA}S{NTA}MLCq{yyy}",
// testTitles2).equals("False,False,Fales,False,Fales,True"));
System.out.println(solution(6, "{xxx}h{cQ}N{vF}u{XTA}S{NTA}MLCq{yyy}", testTitles2)
.equals("False,False,False,False,False,True"));
// System.out.println(solution(3, "a{bdc}efg",
// testTitles3).equals("True,Trur,Fales"));
System.out.println(solution(3, "a{bdc}efg", testTitles3).equals("True,True,False"));
}
}
学习总结
这道题虽然是"简单题", 但一点都不简单。考察到了递归和字符串解析以及双指针等知识点。关联的知识点多,需要我们对基础更加熟悉才能有好的思路。后续可能会用python做一次。