如何在不知道实际模式的情况下检查字符串中的重复模式?

时间:2016-11-11 00:24:58

标签: java string string-parsing

例如,我有一个字符串," fbrt fuifig fbrt "。我想找出一个字符序列是否在字符串中重新出现,但我不知道该字符序列是什么。在这种情况下,它是 fbrt

我考虑过将字符串分成一堆单个单词,然后检查单词是否相同,但在解析较长的字符串时很快就会变得低效。

目前,我实施了上述想法,但肯定有更好的主意。

String s = "fbrtfuifigfbrt";
ArrayList<String> words = new ArrayList<String>(s.length() * s.length());

for(int outerLoop = 0; outerLoop <= s.length(); outerLoop++){
    for(int nestedLoop = 0; nestedLoop <= s.length(); nestedLoop++){
        words.add(fileContents.substring(outerLoop, nestedLoop));
    }
}
//I could dump the ArrayList in a HashSet and check if they are the same size, 
//then find those elements, etc. 
//but that goes along with the above code, and I would prefer to use a more efficient method

3 个答案:

答案 0 :(得分:2)

对此没有很好的优化。你将最终得到某种蛮力解决方案。

类似的东西:

String myString = "abcabcbbb";
//for each char
for (int i = 0; i < myString.length(); i++) {
    //for each substring starting with that char
    int maxSubStringLen = Math.floorDiv(myString.length() - i, 2);
    for (int j = 1; j <= maxSubStringLen; j++) {
        //get the substring
        String subString = myString.substring(i, i + j);
        int repetitionIndex = i + j;
        String repetition = myString.substring(repetitionIndex, repetitionIndex + subString.length());

        //does the substring repeat?
        if (subString.equals(repetition)) {
            System.out.println(subString);
        }
    }
}

这只是打印出马赫的所有子串。您可以将print语句替换为您想要使用的任何内容。

答案 1 :(得分:2)

Java中的工作解决方案:

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        String test1 = "fbrtfuifigfbrt";
        String test2 = "abcdabcd";
        String test3 = "fbrtxibrjkfbrt";
        System.out.println(findRepetitions(test1));
        System.out.println(findRepetitions(test2));
        System.out.println(findRepetitions(test3));
    }

    private static List<String> findRepetitions(String string) {
        List<String> patternsList = new ArrayList<>();
        int length = string.length();
        for (int i = 0; i < length; i++) { // search the first half
            int limit = (length - i) / 2; // candidates can't be longer than half the remaining length
            for (int j = 1; j <= limit; j++) {
                int candidateEndIndex = i + j;
                String candidate = string.substring(i, candidateEndIndex);
                if (string.substring(candidateEndIndex).contains(candidate)) {
                    patternsList.add(candidate);
                }
            }
        }
        return patternsList;
    }
}

输出:

[f, fb, fbr, fbrt, b, br, brt, r, rt, t, f, i, f]
[a, ab, abc, abcd, b, bc, bcd, c, cd, d]
[f, fb, fbr, fbrt, b, br, brt, r, rt, t, b, br, r]

正如其他人已经说过的那样,如果你不知道模式的长度或任何其他适用的限制,那么就没有简单的优化。

如果你想天真地丢弃像ffbfbr这样的子模式,这些子模式只是因为它们是最长{{1}的子字符串而被计算在内模式,你可以使内部fbrt向下计数,从for下降到1,这样你就可以先找到更长的模式,然后检查下一个模式是否是之前找到的模式的子字符串将它们添加到列表中。像这样:

limit

然而,这会阻止您在import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { String test1 = "fbrtfuifigfbrt"; String test2 = "abcdabcd"; String test3 = "fbrtxibrjkfbrt"; // "br" is a pattern but this version won't find it System.out.println(findRepetitions(test1)); System.out.println(findRepetitions(test2)); System.out.println(findRepetitions(test3)); } private static List<String> findRepetitions(String string) { List<String> patternsList = new ArrayList<>(); int length = string.length(); for (int i = 0; i < length; i++) { // search the first half int limit = (length - i) / 2; // candidates can't be longer than half the remaining length for (int j = limit; j >= 1; j--) { int candidateEndIndex = i + j; String candidate = string.substring(i, candidateEndIndex); if (string.substring(candidateEndIndex).contains(candidate)) { boolean notASubpattern = true; for (String pattern : patternsList) { if (pattern.contains(candidate)) { notASubpattern = false; break; } } if (notASubpattern) { patternsList.add(candidate); } } } } return patternsList; } } 中找到br,如输出所示(并且它会使具有许多不同模式的字符串的算法速度变慢) ):

fbrtxzbrjkfbrt

因此天真部分。当然,你可以包含更多的内部循环,以确保找不到被丢弃的候选人自己&#34;在原始字符串中,在实际丢弃它们之前......等等。这取决于你想要搜索的渗透程度。

答案 2 :(得分:1)

您需要有两个迭代器,第一个指针是整个字符串的全局迭代器,第二个迭代器用作搜索指针。假设第一个迭代器指向示例中的char“f”。我们需要在全局迭代器之后找到“f”的所有位置。对于在全局迭代器之后找到的每个“f”,我们需要在全局迭代器和局部迭代器之后逐个比较字符(想想这两个指针以相同的速度移动直到它们指向不同的字符)。一旦本地迭代器到达字符串的末尾,就可以将全局迭代器向前移动一个字符(是的,如果你的字符串中有n个字符,你需要这样做n次)。

我很抱歉代码是用C ++编写的,但Java中的逻辑是相同的。

更新: 还有另一种方法来执行任务。一种流行的解决方案是使用后缀树来存储您的文本。然后,您可以使用任何给定的子字符串搜索后缀树,以查找整个文本中给定子字符串的出现次数。树的构建是O(n),并且搜索子字符串取决于字母表的大小,如果您只使用英文字母,则为26。因此,如果要查找所有重复出现的模式,则只需要搜索给定文本的每个子字符串。哪个只有O(n ^ 2)。所以这个算法比我提出的算法具有整体优势。但是如果你不需要性能,我的算法肯定会满足你的需要,因为它简单易行。

#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main(int argc, const char * argv[]) {
    string s = "sdfssdddfssss";
    int pairCount = 0;
    vector<string> rep;
    for (int i = 0; i < s.length(); i++)
    {
        vector<int> idx;
        //find all index of all same char as s[i] after i
        //Note: You can optimize this by creating a map of index of 26 letters.
        for (int j = i+1; j < s.length(); j++)
            if (s[i] == s[j]) idx.push_back(j);
        int offset = 0;
        for (int j = 0; j < idx.size(); j++)
        {
            while (s[i+offset] == s[idx[j]+offset])
            {
                cout << "Pair found! " << s.substr(i, offset+1) << " " << i << " " << idx[j] << " " << offset + 1 << endl;
                pairCount++;
                offset++;
            }
            offset = 0;
        }
    }
    cout << "Pair count: " << pairCount;
    return 0;
}