青训营刷题-25

198 阅读5分钟

25 创意标题匹配

在广告平台中,为了给广告主一定的自由性和效率,允许广告主在创造标题的时候以通配符的方式进行创意提交。线上服务的时候,会根据用户的搜索词触发的 bidword 对创意中的通配符(通配符是用成对 {} 括起来的字符串,可以包含 0 个或者多个字符)进行替换 ,用来提升广告投放体验。例如:“{末日血战} 上线送 SSR 英雄,三天集齐无敌阵容!”,会被替换成“帝国时代游戏下载上线送 SSR 英雄,三天集齐无敌阵容!”。给定一个含有通配符的创意和一句标题,判断这句标题是否从该创意替换生成的。

输入描述

输入包含以下部分:

  1. 第一行:一个整数 N,代表有 N 个标题。
  2. 第二行:一个字符串,代表含有通配符的创意,创意中包含大小写英文字母和成对的花括号 {}
  3. 接下来的 N:每行一个字符串,代表一个标题,每个标题只包含大小写英文字母。

限制条件

  • 1 <= N <= 10
  • 每个标题和创意的长度均小于等于 100
  • 通配符 {} 内的字符串可以为空或包含多个字符

输出描述

输出 N 行,每行输出 "True""False",判断对应的标题是否可以通过替换创意中的通配符生成。

示例

示例 1

输入

4
ad{xyz}cdc{y}f{x}e
adcdcefdfeffe
adcdcefdfeff
dcdcefdfeffe
adcdcfe

输出

True
False
False
True

说明

  • adcdcefdfeffe 可以看作 ad{}cdc{efd}f{eff}e 或者 ad{}cdc{efdfe}f{f}e,符合创意 ad{xyz}cdc{y}f{x}e
  • adcdcefdfeffdcdcefdfeffe 不符合创意的替换规则。
  • adcdcfe 可以看作 ad{}cdc{}f{}e,符合创意。

解题思路

本问题需要判断给定的标题是否可以通过替换创意中的通配符 {} 生成。每个通配符可以替换为任意长度(包括空字符串)的任意字符序列。为此,可以将创意字符串分解为固定部分和通配符部分,然后逐一匹配标题。

1. 解析创意字符串

首先,将创意字符串分解为固定部分和通配符部分。通配符用 {} 括起来,固定部分为字母序列。例如,创意 ad{xyz}cdc{y}f{x}e 可以分解为:

  • 固定部分:"ad", "cdc", "f", "e"
  • 通配符部分:"{xyz}", "{y}", "{x}"

2. 匹配逻辑

对于每个标题,按照解析后的顺序与创意进行匹配:

  • 固定部分匹配:标题中对应位置必须精确匹配固定部分。
  • 通配符部分匹配:通配符可以匹配标题中的任意长度字符串(包括空字符串)。

匹配过程可以采用递归回溯的方法,尝试所有可能的通配符替换方式,判断是否存在一种替换方式使得整个标题符合创意模板。

3. 递归匹配函数

定义一个递归函数 matchHelper(title, tIndex, parts, pIndex),其中:

  • title:待匹配的标题。
  • tIndex:当前匹配到标题的索引。
  • parts:解析后的创意部分列表。
  • pIndex:当前匹配到创意的部分索引。

递归终止条件

  • 当所有创意部分都匹配完毕时,检查标题是否也完全匹配。

匹配步骤

  1. 如果当前创意部分是固定部分,检查标题当前位置是否匹配。如果匹配,继续递归匹配下一个部分。
  2. 如果当前创意部分是通配符,尝试匹配标题当前位置的任意长度字符串(包括空字符串),并递归匹配剩余部分。

4. 复杂度分析

  • 时间复杂度:最坏情况下为 O(m^n),其中 m 是标题的长度,n 是创意中通配符的数量。但由于题目限制 N <= 10 和字符串长度 <= 100,递归方法是可行的。
  • 空间复杂度O(m + n),用于递归调用栈和存储解析后的创意部分。

5. 注意

题目代码中提供的示例字符串是有误的,比如False写成了FalesTrue写成了Trur

System.out.println(solution(4, "ad{xyz}cdc{y}f{x}e", testTitles1).equals("True,False,Fales,True"));

算法实现

import java.util.*;

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