06:最短前缀

本文介绍如何使用字典树(Trie)结构来寻找一组单词中每个单词的最短且无歧义的前缀标识符。通过构建字典树,可以高效地处理大量单词并找出它们独有的缩写形式,确保即使在单词前缀相同时也能准确区分。

总时间限制:
1000ms
内存限制:
65536kB
描述
一个字符串的前缀是从该字符串的第一个字符起始的一个子串。例如 "carbon"的字串是: "c", "ca", "car", "carb", "carbo", 和 "carbon"。注意到这里我们不认为空串是字串, 但是每个非空串是它自身的字串. 我们现在希望能用前缀来缩略的表示单词。例如, "carbohydrate" 通常用"carb"来缩略表示. 现在给你一组单词, 要求你找到唯一标识每个单词的最短前缀
在下面的例子中,"carbohydrate" 能被缩略成"carboh", 但是不能被缩略成"carbo" (或其余更短的前缀) 因为已经有一个单词用"carbo"开始
一个精确匹配会覆盖一个前缀匹配,例如,前缀"car"精确匹配单词"car". 因此 "car" 是 "car"的缩略语是没有二义性的 , “car”不会被当成"carriage"或者任何在列表中以"car"开始的单词.
输入
输入包括至少2行,至多1000行. 每行包括一个以小写字母组成的单词,单词长度至少是1,至多是20.
输出
输出的行数与输入的行数相同。每行输出由相应行输入的单词开始,后面跟着一个空格接下来是相应单词的没有二义性的最短前缀标识符。
样例输入
carbohydrate
cart
carburetor
caramel
caribou
carbonic
cartilage
carbon
carriage
carton
car
carbonate
样例输出
carbohydrate carboh
cart cart
carburetor carbu
caramel cara
caribou cari
carbonic carboni
cartilage carti
carbon carbon
carriage carr
carton carto
car car
carbonate carbona


模板字典树


#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

struct Trie
{
    int cnt;
    Trie *next[26];
    Trie()              //构造函数
    {
        cnt = 1;
        for(int i = 0; i < 26; i++)
            next[i] = NULL;
    }
};

void Insert(char *w, Trie *rt)
{
    Trie *p = rt;
    int i = 0;
    while(w[i])
    {
        int k = w[i] - 'a';
        if(p->next[k] != NULL)             // 有就赋值没有就新建
            p->next[k]->cnt++;              // 数量
        else
            p->next[k] = new Trie;
        p = p->next[k];
        i++;
    }
}

void Search(char *w, Trie *rt)
{
    Trie *p = rt;
    if(rt == NULL)
        return ;
    int i = 0;
    while(w[i])
    {
        int  k = w[i] - 'a';
        if(p->next[k])
        {
            if(p->next[k]->cnt == 1 || i == strlen(w) - 1)      // 一直到他是唯一或者单词末尾
            {
                cout<<w[i]<<endl;
                return ;
            }
            else
            {
                cout<<w[i];
                p = p->next[k];
            }
        }
        i++;
    }
}


int main()
{

    char p[1000 + 5][30];
    Trie *t = new Trie;
    int i = 0;
    while(~scanf("%s",p[i]))
    {
        Insert(p[i],t);
        i++;
    }
    for(int j = 0; j < i; j++)
    {
        printf("%s ",p[j]);
        Search(p[j],t);
    }
    return 0;
}




<think>嗯,用户问的是如何通过算法找到字符串前缀回文串。根据之前的引用内容,这个问题应该是关于在字符串前面添加字符,使其成为回文,并且要找到的那个。我需要回忆一下相关的算法。 首先,我得理解问题。比如,输入的字符串是“aacecaaa”,输出是“aaacecaaa”。看起来是在前面加了一个“a”,使得整个字符串变成回文。那怎么找到需要添加的字符呢? 可能的思路是找到原字符串前缀回文子串。比如,如果字符串本身有一部分已经是回文的前缀,那么剩下的部分翻转后加到前面就可以了。例如,假设原字符串是s,如果存在一个长的前缀回文子串s[0...k],那么剩下的部分s[k+1...n-1]翻转后加到前面,就能形成的回文。 那如何高效地找到这个前缀回文呢?暴力方法是检查每个可能的前缀是否是回文,但这样时间复杂度会是O(n²),可能不够高效。 这时候想到KMP算法中的部分匹配表(LPS数组),也就是前缀后缀匹配。这个原本用于字符串匹配中的预处理步骤,可以用来寻找长的回文前缀。或者是否有其他方法? 另外,回文可以看作字符串和它的反转相匹配。例如,将字符串s与它的反转s'连接起来,中间加一个特殊字符分隔,比如s + '#' + reversed_s,然后计算这个新字符串的LPS数组。大的LPS值可能对应长的回文前缀。 比如,对于s = "aacecaaa",它的反转是"aaacecaa"。将它们拼接为"aacecaaa#aaacecaa",然后构建LPS数组。大的值可能出现在末尾,表示原字符串中有多长的前缀和反转后的后缀匹配。这样就能得到长回文前缀的长度。 假设这样得到长度是7(比如原字符串的前7个字符是回文),那么剩下的一个字符需要被翻转后添加到前面。比如原字符串剩下的部分是一个字符,翻转后加在前面,这样总长度就是原字符串长度加上需要添加的字符数。 具体步骤可能如下: 1. 将字符串s和其反转s'拼接,中间加上分隔符,得到新的字符串str = s + '#' + reversed_s。 2. 构建str的LPS数组。 3. 找到str末尾的LPS值,这个值代表s的前缀回文长度。 4. 将s中剩余的部分(即从该长度到末尾的部分)翻转,并添加到原字符串前面,得到回文。 例如,对于示例1中的s = "aacecaaa",反转是"aaacecaa"。拼接后的字符串是"aacecaaa#aaacecaa"。计算LPS数组后,一个位置的LPS值应该是7,说明前缀回文长度是7。剩下的部分是第7个字符之后的“a”,所以需要将剩下的部分(“a”)的反转(还是“a”)加到前面,得到“a” + “aacecaaa” = “aaacecaaa”,但原示例的输出是“aaacecaaa”,可能我哪里弄错了。或者可能需要更仔细的分析。 或者可能,原字符串前缀回文长度是k,那么需要添加的是s的后面n - k个字符的反转。例如,如果s的前k个字符是回文,那么剩下的n -k 个字符的反转添加到前面,就形成了回文。 比如,s = "aacecaaa",它的前缀回文是“aacecaa”(长度7吗?原字符串长度是8?那剩下的字符一个a,反转后加在前面,所以得到的结果是“a” + “aacecaaa” = “aaacecaaa”,与示例输出一致。所以这个方法是可行的。 那如何实现这个LPS的计算呢?KMP中的LPS数组的构建过程是这样的:对于模式串,逐个字符处理,维护一个len变量,初始为0。然后从第二个字符开始,如果当前字符等于模式串[len],则len++,否则回退。然后LPS[i] = len。 在拼接后的字符串中,我们需要找到整个字符串前缀后缀匹配长度,这对应于原字符串前缀回文。因为反转后的字符串的后缀对应原字符串前缀的反转,所以当拼接后的字符串长前后缀匹配时,原字符串前缀和反转后的部分的后缀(即原字符串前缀的反转)相同,说明原字符串前缀是回文。 因此,通过这种方法可以找到长的回文前缀,从而确定需要添加的字符。 那么具体实现步骤应该是: 1. 输入字符串s。 2. 创建反转字符串rev_s。 3. 拼接新字符串l = s + '#' + rev_s。 4. 计算l的LPS数组。 5. 找到l的一个位置的值max_len,即长前后缀匹配长度。 6. 将rev_s的前缀(即s的后面的部分的反转)添加到s前面。具体来说,需要添加的是rev_s的前 (n - max_len) 个字符,然后加上原字符串s。 例如,对于s的长度为n,max_len是原字符串长的回文前缀的长度。剩下的部分长度是n - max_len,这部分的反转应该加到前面。例如,s剩下的部分是s.substring(max_len),反转后就是rev_s的前 (n - max_len) 个字符。所以结果应该是 rev_s.substring(0, n - max_len) + s。 这样得到的字符串就是的回文。 那如何编写代码呢?例如,用Python的话: def shortestPalindrome(s): rev_s = s[::-1] l = s + '#' + rev_s n = len(l) lps = [0] * n for i in range(1, n): len_matched = lps[i-1] while len_matched >0 and l[i] != l[len_matched]: len_matched = lps[len_matched-1] if l[i] == l[len_matched]: len_matched +=1 lps[i] = len_matched max_len = lps[-1] return rev_s[:len(s) - max_len] + s 测试示例1,s = "aacecaaa",反转是"aaacecaa"。拼接后的字符串是"aacecaaa#aaacecaa"。计算LPS数组的一个位置的值,假设是7。那么返回rev_s的前1个字符(8-7=1),即"a",加上原s,得到"aaacecaaa",正确。 另一个示例,比如s = "abcd",反转是"dcba"。拼接后的字符串是"abcd#dcba"。LPS数组的后一位应该是0,因为abcd和dcba没有公共前后缀。所以返回dcba的前4-0=4个字符,即dcba + abcd → dcbabcd。这样结果就是dcbabcd,正确。 那这个算法的时间复杂度是O(n),因为LPS的计算是线性的,空间复杂度也是O(n),因为需要存储拼接后的字符串和LPS数组。 所以,这个方法是有效的,并且能够正确找到的回文前缀。 总结来说,算法步骤是利用KMP的LPS数组来高效找到前缀回文,然后通过反转剩余部分添加到前面得到回文。</think>要找到字符串前缀回文串,可以通过以下步骤实现: ### 算法思路 1. **问题核心**:找到原字符串$s$的前缀回文子串。剩余部分的反转即为需要添加的前缀。 2. **KMP的LPS数组**:利用KMP算法中的前缀后缀匹配(LPS)特性,将问题转化为模式匹配问题。 3. **拼接字符串**:将$s$与反转后的字符串$s'$拼接为$s + \# + s'$,计算其LPS数组,末尾值即为前缀回文的长度。 ### 实现步骤 1. **反转字符串**:生成$s'=reverse(s)$。 2. **构建新字符串**:$l = s + \# + s'$。 3. **计算LPS数组**:遍历$l$,记录每个位置的长匹配前后缀。 4. **确定长回文前缀**:取LPS数组末尾的值$max\\_len$。 5. **生成结果**:将$s$中非回文部分的反转添加到原字符串前。 ### 代码示例 ```python def shortestPalindrome(s): rev_s = s[::-1] combined = s + '#' + rev_s lps = [0] * len(combined) for i in range(1, len(combined)): matched = lps[i-1] while matched > 0 and combined[i] != combined[matched]: matched = lps[matched-1] if combined[i] == combined[matched]: matched += 1 lps[i] = matched max_len = lps[-1] return rev_s[:len(s) - max_len] + s ``` ### 示例分析 - **输入**:$s = \text{"aacecaaa"}$ - **反转**:$s' = \text{"aaacecaa"}$ - **拼接字符串**:$\text{"aacecaaa\#aaacecaa"}$ - **LPS末尾值**:$7$(前缀回文为$\text{"aacecaa"}$) - **结果**:$\text{"a" + "aacecaaa" = "aaacecaaa"}$[^1] ### 复杂度 - **时间复杂度**:$O(n)$,LPS数组的构建是线性时间。 - **空间复杂度**:$O(n)$,存储拼接字符串和LPS数组。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值