创意标题匹配问题-解题攻略

132 阅读3分钟

问题描述

在广告平台中,为了给广告主一定的自由性和效率,允许广告主在创造标题的时候以通配符的方式进行创意提交。线上服务的时候,会根据用户的搜索词触发的 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"


核心思路

  1. 模板解析:提取模板中的固定文本序列(非通配符部分)
  2. 标题验证:检查标题是否满足固定文本的顺序和位置要求

完整代码实现

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")));
    }
}

关键算法解析

模板解析流程

  1. 初始化缓冲区buffer收集固定字符

  2. 遇到{时:

    • 如果当前不在通配符内,且缓冲区有内容 → 存入固定序列
    • 标记进入通配符区域
  3. 遇到}时退出通配符状态

  4. 最后处理缓冲区剩余内容

标题验证逻辑

步骤操作示例验证
1检查起始固定部分模板"a{...",标题必须以"a"开头
2顺序查找固定部分在"aXXXcYYY"中查找"c"
3检查结尾固定部分模板"...c}",标题必须以"c"结尾

复杂度分析

  • 时间复杂度

    • 模板解析:O(M) → M为模板长度
    • 单标题验证:O(K*N) → K为固定部分数,N为标题长度
  • 空间复杂度:O(K) → 存储固定部分序列


总结

  1. 预处理优先:提前解析模板可以显著提升验证效率
  2. 边界处理:特别注意模板首尾是否为固定部分
  3. 顺序为王:固定部分必须按顺序出现,但允许中间存在任意内容
  4. 空字符串处理:通配符可以替换为空,但固定部分必须完整保留