创意标题匹配 | 豆包MarsCode AI刷题

81 阅读4分钟

创意标题匹配

#青训营笔记创作活动

问题描述

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

题目地址:www.marscode.cn/practice/9e…

输入输出

输入:n = 4, template = "ad{xyz}cdc{y}f{x}e", titles = ["adcdcefdfeffe", "adcdcefdfeff", "dcdcefdfeffe", "adcdcfe"]
输出:"True,False,False,True"

思路

{xxx}可以看作一个通配符,将模板串看成ad*cdc*f*e,这里*可以匹配任意个,也可以不匹配~ 咱们只考虑一个title

比如说 sadcdcefdfeffeadcdcefdfeffe, p: adcdcfead*cdc*f*e。 从后往前看,假设i,ji, j 分别指向s,ts, t 的末尾

如果 s[i]==p[j]s[i] == p[j] ,问题变成s[0...i1],p[0...j1]s[0...i - 1], p[0...j-1] 是否匹配,可以看出这是一个原问题的子问题。

定义dfs(i,j)dfs(i, j) 表示s[0,i],p[0,j]s[0,i], p[0,j] 是否能够匹配:

  • 如果p[j]p[j] \neq '*' , 问题变成(s[i]==p[j]) && dfs(i1,j1)(s[i] == p[j]) \ \&\& \ dfs(i - 1, j - 1)

  • 如果p[j]==p[j] == '*' ,说明这个位置可以匹配任意长度的字符,只要有一个匹配成功就是成功,所以子问题要 起来。

    • 不匹配,问题变成 dfs(i, j - 1)
    • 匹配一个,问题变成 dfs(i - 1, j - 1)
    • ......
    • 都匹配,问题变成 dfs(-1, j - 1)

递归边界,dfs(1,1)=true;  dfs(0,k)=true,  where  p[0,k]=dfs(-1, -1) = true; \ \ dfs(0, k) = true, \ \ where \ \ p[0,k] = '*' . 由于有很多重复的状态,需要一个数组进行记忆化,定义为memomemo, 初始值为-1 .

class Solution {
    char[] s, t;
    int[][] memo;
    int len;
    public boolean isMatch(String title, String p) {
        s = title.toCharArray();
        t = p.toCharArray();
        int m = s.length, n = t.length;
        for (char c : t) {
            if (c != '*') break;
            len ++;
        }
        memo = new int[m][n];
        for (int[] row : memo) Arrays.fill(row, -1);
​
        return dfs(m - 1, n - 1);
    }
​
    private boolean dfs(int i, int j) {
        if (j < 0) return i < 0;
        if (i < 0) return j < len;
        if (memo[i][j] != -1) return memo[i][j] == 1;
        boolean ans = true;
        if (t[j] == '*') {
            boolean f = false;
            for (int k = i; k >= -1; k --) {
                f |= dfs(k, j - 1);
            }
            ans &= f;
        } else ans &= (s[i] == t[j]) && dfs(i - 1, j - 1);
        memo[i][j] = ans ? 1 : 0;
        return ans;
    }
}

时间复杂度O(M2×N)O(M ^ 2 \times N) ,其中M,NM, N 分别是s,ps, p 的长度。

有没有什么优化的地方呢?我们看p[j]==p[j] == '*'的部分:

dfs(i,j)=dfs(i,j1)  dfs(i1,j1)  ...  dfs(1,j1)dfs(i, j) = dfs(i, j - 1) \ || \ dfs(i - 1, j - 1) \ || \ ... \ ||\ dfs(-1, j - 1) , 那么dfs(i1,j)dfs(i - 1, j) 等于什么呢?

dfs(i1,j)=dfs(i1,j1)  ...  dfs(1,j1)dfs(i - 1, j) = dfs(i - 1, j - 1) \ || \ ... \ ||\ dfs(-1, j - 1) ,

可以发现: dfs(i,j)=dfs(i,j1)  dfs(i1,j)dfs(i, j) = dfs(i, j - 1) \ || \ dfs(i - 1, j) ,将O(M)O(M) 优化到O(1)O(1) ,整体时间复杂度变成O(M×N)O(M \times N)

class Solution {
    char[] s, t;
    int[][] memo;
    int len;
    public boolean isMatch(String title, String p) {
        s = title.toCharArray();
        t = p.toCharArray();
        int m = s.length, n = t.length;
        for (char c : t) {
            if (c != '*') break;
            len ++;
        }
        memo = new int[m][n];
        for (int[] row : memo) Arrays.fill(row, -1);
​
        return dfs(m - 1, n - 1);
    }
​
    private boolean dfs(int i, int j) {
        if (j < 0) return i < 0;
        if (i < 0) return j < len;
        if (memo[i][j] != -1) return memo[i][j] == 1;
        boolean ans = true;
        if (t[j] == '*') {
            ans &= dfs(i, j - 1) || dfs(i - 1, j);
        } 
        else ans &= (s[i] == t[j]) && dfs(i - 1, j - 1);
        memo[i][j] = ans ? 1 : 0;
        return ans;
    }
}

递归递归,有递有归,我们可以省区递的部分,只保留归,也就是写成forfor 循环的形式。

class Solution {
    public boolean isMatch(String st, String p) {
        var s = st.toCharArray();
        var t = p.toCharArray();
        int m = s.length, n = t.length;
        int len = 0;
        for (char c : t) {
            if (c != '*') break;
            len ++;
        }
        boolean[][] dp = new boolean[m + 1][n + 1];
        for (int j = 0; j <= len; j ++) dp[0][j] =  true;
        for (int i = 0; i < m; i ++) {
            for (int j = 0; j < n; j ++) {
                dp[i + 1][j + 1] = true;
                if (t[j] == '*') {
                    dp[i + 1][j + 1] &= dp[i + 1][j] || dp[i][j + 1];
                } else dp[i + 1][j + 1] &= (s[i] == t[j]) && dp[i][j];
            }
        }
        return dp[m][n];
    }
}

总体时间复杂度O(K×M2×N),K=len(titles)O(K \times M^2 \times N), K = len(titles)

最终代码:

public class Main {
    public static String solution(int n, String template_, String[] titles) {
        // Please write your code here
        int m = template_.length();
        StringBuilder builder = new StringBuilder(m);
        for (int i = 0; i < m; i ++) {
            if (template_.charAt(i) == '{') {
                for (int j = i + 1; j < m; j ++) {
                    if (template_.charAt(j) == '}') {
                        i = j;
                        break;
                    }
                }
                builder.append('*');
            }
            else {
                builder.append(template_.charAt(i));
            }
        }
        char[] p = builder.toString().toCharArray();
        int len = 0;
        for (char c : p) {
            if (c != '*') break;
            len ++;
        }
        m = p.length;
        var bd = new StringBuilder();
        for (int k = 0; k < titles.length; k ++) {
            char[] s = titles[k].toCharArray();
            int q = s.length;
            boolean[][] dp = new boolean[q + 1][m + 1];
            for (int j = 0; j <= len; j ++) dp[0][j] =  true;
            for (int i = 0; i < q; i ++) {
                for (int j = 0; j < m; j ++) {
                    dp[i + 1][j + 1] = true;
                    if (p[j] == '*') {
                        dp[i + 1][j + 1] &= dp[i + 1][j] || dp[i][j + 1];
                    } else {
                        dp[i + 1][j + 1] &= (p[j] == s[i]) & dp[i][j];
                    }
                }
            }
            bd.append(dp[q][m] ? "True" : "False");
            if (k < titles.length - 1) bd.append(',');
        }
        return bd.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,False,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,True,False"));
    }
}