问题描述
在广告平台中,为了给广告主一定的自由性和效率,允许广告主在创造标题的时候以通配符的方式进行创意提交。线上服务的时候,会根据用户的搜索词触发的 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"
样例3:
输入:
n = 5, template = "{abc}xyz{def}", titles = ["xyzdef", "abcdef", "abxyzdef", "xyz", "abxyz"]
输出:"True,False,True,True,True"
核心思路
- 模板解析:提取模板中的固定文本序列(非通配符部分)
- 标题验证:检查标题是否满足固定文本的顺序和位置要求
完整代码实现
import java.util.ArrayList;
import java.util.List;
public class AdTemplateValidator {
// 主入口方法
public String validateTitles(int n, String template, List<String> titles) {
List<String> fixedParts = parseTemplate(template);
boolean startsWithFixed = !template.startsWith("{") && !fixedParts.isEmpty();
boolean endsWithFixed = !template.endsWith("}") && !fixedParts.isEmpty();
List<String> results = new ArrayList<>();
for (String title : titles) {
results.add(checkTitle(title, fixedParts, startsWithFixed, endsWithFixed) ? "True" : "False");
}
return String.join(",", results);
}
// 解析模板得到固定部分序列
private List<String> parseTemplate(String template) {
List<String> fixedParts = new ArrayList<>();
StringBuilder buffer = new StringBuilder();
boolean inWildcard = false;
for (char c : template.toCharArray()) {
if (c == '{') {
if (!inWildcard && buffer.length() > 0) {
fixedParts.add(buffer.toString());
buffer.setLength(0);
}
inWildcard = true;
} else if (c == '}') {
inWildcard = false;
} else {
if (!inWildcard) buffer.append(c);
}
}
if (buffer.length() > 0) {
fixedParts.add(buffer.toString());
}
return fixedParts;
}
// 验证单个标题是否合法
private boolean checkTitle(String title, List<String> fixedParts,
boolean startsWithFixed, boolean endsWithFixed) {
if (fixedParts.isEmpty()) return true;
int currentPos = 0;
final int totalParts = fixedParts.size();
for (int i = 0; i < totalParts; i++) {
String part = fixedParts.get(i);
// 处理第一个固定部分
if (i == 0 && startsWithFixed) {
if (!title.startsWith(part)) return false;
currentPos = part.length();
continue;
}
// 处理最后一个固定部分
if (i == totalParts - 1 && endsWithFixed) {
if (!title.endsWith(part)) return false;
int expectedStart = title.length() - part.length();
if (currentPos > expectedStart) return false;
currentPos = title.length();
continue;
}
// 处理中间固定部分
int foundIndex = title.indexOf(part, currentPos);
if (foundIndex == -1) return false;
currentPos = foundIndex + part.length();
}
// 最终位置校验
return endsWithFixed ? (currentPos == title.length()) : true;
}
// 测试用例
public static void main(String[] args) {
AdTemplateValidator validator = new AdTemplateValidator();
// 样例1
System.out.println(validator.validateTitles(4, "ad{xyz}cdc{y}f{x}e",
List.of("adcdcefdfeffe", "adcdcefdfeff", "dcdcefdfeffe", "adcdcfe")));
// 样例2
System.out.println(validator.validateTitles(3, "a{bdc}efg",
List.of("abcdefg", "abefg", "efg")));
// 样例3
System.out.println(validator.validateTitles(5, "{abc}xyz{def}",
List.of("xyzdef", "abcdef", "abxyzdef", "xyz", "abxyz")));
}
}
关键算法解析
模板解析流程
-
初始化缓冲区
buffer收集固定字符 -
遇到
{时:- 如果当前不在通配符内,且缓冲区有内容 → 存入固定序列
- 标记进入通配符区域
-
遇到
}时退出通配符状态 -
最后处理缓冲区剩余内容
标题验证逻辑
| 步骤 | 操作 | 示例验证 |
|---|---|---|
| 1 | 检查起始固定部分 | 模板"a{...",标题必须以"a"开头 |
| 2 | 顺序查找固定部分 | 在"aXXXcYYY"中查找"c" |
| 3 | 检查结尾固定部分 | 模板"...c}",标题必须以"c"结尾 |
复杂度分析
-
时间复杂度:
- 模板解析:O(M) → M为模板长度
- 单标题验证:O(K*N) → K为固定部分数,N为标题长度
-
空间复杂度:O(K) → 存储固定部分序列
总结
- 预处理优先:提前解析模板可以显著提升验证效率
- 边界处理:特别注意模板首尾是否为固定部分
- 顺序为王:固定部分必须按顺序出现,但允许中间存在任意内容
- 空字符串处理:通配符可以替换为空,但固定部分必须完整保留