【问题标题】:Number of substrings that doesn't contain given strings (Large constraint)不包含给定字符串的子字符串数(大约束)
【发布时间】:2018-05-15 14:26:14
【问题描述】:

我最近在网上发现了一个有趣的问题。简述如下:

注意整体时间限制不应该存在1.00s(时间复杂度

现在学生 A 发现了一个仅由小写字符组成的字符串。他想给学生 B 剪一个子串作为礼物。学生 B 有一个他认为“丑陋”的字符串列表。你能帮助学生 A 找到切割不包含任何“丑陋”字符串的子字符串的方法的数量吗? (注意相同的子字符串但来自不同的位置也算)。

Example:
Student A: abcdabcdab
Ugly strings: cd, da

Output: 17

Explanation:
    The 17 cuttings are "a" (appears 3 times), "ab" (appears 3 times), 
    "abc" (appears 2 times), "bc" (appears 2 times), "b" (appears 3 times),
    "c" (appears 2 times) and "d" (appears 2 times)

我最初认为这是一个简单的问题,但这个限制非常大。学生 A 的字符串最大长度为 100000,而丑陋的字符串最多可以有 500000 个,最大长度为 500000。

我尝试使用后缀 trie 来解决这个问题,但由于内存限制而惨遭失败。任何人都可以提出解决问题的可能方法。这是一些与数据结构相关的高级问题,例如后缀数组

建议使用任何编程语言的代码,并带有适当的描述。因为我觉得如果有实际的代码可以研究会更好。

【问题讨论】:

    标签: string substring trie suffix-array


    【解决方案1】:

    由于从不同位置开始的相等子串算作不同的子串,所以长度为 n 的字符串的最大子串数为 n*(n + 1) / 2。(从位置 0 开始的 n 个子串,n-1 个子串从位置 1 开始,依此类推)。

    如果一个丑陋的字符串包含在从位置 p 开始的长度为 q 的子串中,那么所有从 p 开始且长度 > q 的子串也将包含该丑陋的字符串。

    如果丑陋的字符串比子字符串本身长,它将不匹配。

    我的第一次尝试是这样的:

    String ugly[]; // is provided somehow; at most 500000 with max length of 500000
    String student; // the String to cut into substrings, max length 100000
    long num = 0;
    
    ugly.sort(); // by length
    
    for (int start = 0; start < student.size() - 1, ++start) {
        for (int end = start + 1; end < student.size(); ++end) {
            String s = student.substr(start, end);
            int lgth = s.size();
            int u = 0;
            while (lgth >= ugly[u].size()) {
                if (s.contains(ugly[u])) break;
                ++u;
            }
            if (lgth < ugly[u].size()) {
                ++num; // we checked all potentially matching uglies
            } else {
                break; // leave the inner loop and 
                       // start with the next position
            }
        }
    }
    

    我的第二次尝试将采用另一种方法。如果这个任务必须执行多次,我会开始开发这个。

    如果我有一个字符串 student 和一个长度为 p 的丑陋字符串在某处匹配,则字符串 student 可以分为两部分:第一部分以丑陋字符串的前 p-1 个字符结尾第二部分以丑陋字符串的最后 p-1 个字符开头。

    这可以重复,直到丑陋的字符串在任何地方都不匹配。然后我们有许多子字符串和一个不匹配的丑字符串。因此,这个丑陋的字符串可以被丢弃。

    对所有丑陋的字符串重复此操作,您将获得与任何丑陋字符串都不匹配的“最长”子字符串列表。 现在您可以遍历此列表并将 length * (length + 1)/2 添加到最终结果中。

    它看起来像

    String ugly[]; // as before
    String student; // as before
    long num = 0;
    Vector substrs = new Vector();
    
    ugly.sort(); // by length
    substrs.add(student);
    
    void splitStr(String str2split, String pattern, Vector result)
    {
        if (str2split.size() < pattern.size()) {
            result.add(str2split);
            return;
        } else {
            int pos = str2split.contains(pattern); // returns position, -1 if not found
            if (pos >= 0) { // found
                String s1 = str2split.substr(0, pos + pattern.size() - 1);
                String s2 = str2split.substr(pos + 1, str2split.size());
                // add s1 and repeat split on s2
                result.add(s1);
                splitStr(s2, pattern, result);
            } else {
                // not found, entire string is ok
                result.add(str2split);
            }
        }
    }
    
    for (int u = 0; u < ugly.size(); ++u) {
        Vector newSubstrs = new Vector();
        String ugly2test = ugly[u];
        for (int i = 0; i < substrs.size(); ++i) {
            String t = substrs.get(i);
            splitStr(t, ugly2test, newSubstrs);
        }
        substrs = newSubstrs;
    }
    
    for (int i = 0; i < substrs.size(); ++i) {
        String s = substrs.get(i);
        num += s.size() * (s.size() + 1) / 2;
    }
    

    注意:这基本上是这个想法。我没有测试任何代码(类似于 java,但可能不会编译),我只是将我的纯文本想法翻译成某种伪代码。

    【讨论】:

    • 你的代码最坏情况下的时间复杂度是O(500000*500000*100000) 我觉得会超过时间限制
    • 嗯,n 等于要拆分为子字符串的字符串的长度,m 等于丑陋模式的数量,操作数为 O(n² m)。 (m * n * (n + 1) / 2)。 (我没有考虑搜索,它增加了另一个 n;因此 O(n^4))。但是发现丑陋的模式会大大加快速度。您可以采取一些措施来减少丑陋模式的数量(例如,可以消除包含较短模式的较长模式),但我也可以假设丑陋模式集无法减少。
    • 您可以利用单字符丑陋模式。您可以将它们全部从原始字符串中删除,从而产生一组子字符串。每一个都可以用我的算法处理。由于长度减少,它会更快。
    • 我认为仅用尽可能的子字符串的部分已经导致超过时间限制。 (O(N^2))
    • 嗯,你必须以某种方式计算它们。显然,如果您有一个长度为 p 的 student 子字符串与任何丑陋的模式都不匹配,您可以将 p(p+1)/2 添加到结果中。所以也许向后退(从某个位置开始的整个子字符串开始,如果你有命中(使用命中的位置)使其更短)会加快速度。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-25
    • 1970-01-01
    • 2011-03-18
    • 2016-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多