web-dev-qa-db-ja.com

指定された配列内の文字の繰り返しシーケンスを見つける方法は?

私の問題は、指定された配列内の文字の繰り返しシーケンスを見つけることです。単に、文字が表示されるパターンを識別するためです。

   .---.---.---.---.---.---.---.---.---.---.---.---.---.---.
1: | J | A | M | E | S | O | N | J | A | M | E | S | O | N |
   '---'---'---'---'---'---'---'---'---'---'---'---'---'---'
   .---.---.---.---.---.---.---.---.---.---.---.---.---.---.---.
2: | R | O | N | R | O | N | R | O | N | R | O | N | R | O | N |
   '---'---'---'---'---'---'---'---'---'---'---'---'---'---'---'
   .---.---.---.---.---.---.---.---.---.---.---.---.
3: | S | H | A | M | I | L | S | H | A | M | I | L |
   '---'---'---'---'---'---'---'---'---'---'---'---'
   .---.---.---.---.---.---.---.---.---.---.---.---.---.---.---.---.---.---.
4: | C | A | R | P | E | N | T | E | R | C | A | R | P | E | N | T | E | R |
   '---'---'---'---'---'---'---'---'---'---'---'---'---'---'---'---'---'---'

以前のデータを考えると、結果は次のようになります。

  1. "JAMESON"
  2. "RON"
  3. "SHAMIL"
  4. "CARPENTER"

質問

  • この問題を効率的に処理するにはどうすればよいですか?
35
brainless

あなたの例では、私の最初のアプローチは

  1. 配列の最初の文字を取得します(最後の例では、Cになります)
  2. 配列内のその文字の次の出現のインデックスを取得します(例:9)
  3. 見つかった場合、文字の2つの出現の間の部分文字列の次の出現を検索します(この場合はCARPENTER)。
  4. 見つかった場合は完了です(結果はこの部分文字列です)。

もちろん、これは可能な配列の非常に限定されたサブセットに対してのみ機能します。同じWordが最初から始まり、途中に浮遊文字がなく、Word内で最初の文字が繰り返されない場合に、繰り返し繰り返されます。しかし、あなたのすべての例はこのカテゴリに分類されます-そして私はおそらくうまくいく可能性がある最も簡単な解決策を好みます:-)

繰り返されるWordに最初の文字が複数回含まれている場合(CACTUSなど)、アルゴリズムを拡張して、最初の文字だけでなく、その文字の後続の出現も探すことができます(そのため、繰り返されるWord全体が見つかります。その部分文字列だけではありません)。

この拡張アルゴリズムは、2番目の例とは異なる結果、つまりRONRONではなくRONを返すことに注意してください。

18
Péter Török

舌の頬O(NlogN)ソリューション

文字列に対してFFTを実行します(文字を数値として扱います)。結果のグラフのすべてのピークは、サブストリングの周期性に対応しています。

25

Pythonでは、次のように正規表現を利用できます。

def recurrence(text):
    import re
    for i in range(1, len(text)/2 + 1):
        m = re.match(r'^(.{%d})\1+$'%i, text)
        if m: return m.group(1)

recurrence('abcabc') # Returns 'abc'

これがJavaまたはCにどのように変換されるかはわかりません。これがPythonが好きな理由の1つだと思います。:-)

6
Marcelo Cantos

最初に、以下のように、コンテナ文字列で繰り返し部分文字列subを見つけるメソッドを記述します。

boolean findSubRepeating(String sub, String container);

コンテナ内の部分文字列を増やしてこのメ​​ソッドを呼び出し続けます。最初に1文字の部分文字列を試し、次に2文字などをcontainer.length/2まで試します。

2
fastcodejava

私の頭に浮かぶ最初のアイデアは、length(S)= Nを分割する長さのすべての反復シーケンスを試すことです。そのような長さは最大N/2なので、これはO(N ^ 2)アルゴリズムになります。

しかし、それは改善できると確信しています...

1
Eyal Schneider

疑似コード

len = str.length
for (i in 1..len) {
   if (len%i==0) {
      if (str==str.substr(0,i).repeat(len/i)) {
         return str.substr(0,i)
      }
   }
}

注:簡潔にするために、私は文字列の「繰り返し」メソッドを発明しています。これは、実際にはJavaの文字列の一部ではありません。 "abc" .repeat(2)= "abcabc"

1

C++の使用:

//Splits the string into the fragments of given size
//Returns the set of of splitted strings avaialble
set<string> split(string s, int frag)
{
    set<string> uni;
    int len = s.length();
    for(int i = 0; i < len; i+= frag)
    {
        uni.insert(s.substr(i, frag));
    }

    return uni;
}

int main()
{

    string out;
    string s = "carpentercarpenter";
    int len = s.length();

      //Optimistic approach..hope there are only 2 repeated strings
      //If that fails, then try to break the strings with lesser number of
      //characters
    for(int i = len/2; i>1;--i)
    {
        set<string> uni = split(s,i);
        if(uni.size() == 1)
        {
            out = *uni.begin();
            break;
        }
    }

    cout<<out;
    return 0;

}
1
Asha

すべてのキャラクターを配列e.xに配置します。 a []

i=0; j=0;
for( 0 < i < count ) 
{
if (a[i] == a[i+j+1])
    {++i;}
else
    {++j;i=0;}
}

次に、(i/j)の比率=配列の繰り返し回数。 ijの制限に注意する必要がありますが、これは簡単な解決策です。

0
user2617898

そしてここに具体的な作業例があります:

/* find greatest repeated substring */
char *fgrs(const char *s,size_t *l)
{
  char *r=0,*a=s;
  *l=0;
  while( *a )
  {
    char *e=strrchr(a+1,*a);
    if( !e )
      break;
    do {
      size_t t=1;
      for(;&a[t]!=e && a[t]==e[t];++t);
      if( t>*l )
        *l=t,r=a;
      while( --e!=a && *e!=*a );
    } while( e!=a && *e==*a );
    ++a;
  }
  return r;
}

  size_t t;
  const char *p;
  p=fgrs("BARBARABARBARABARBARA",&t);
  while( t-- ) putchar(*p++);
  p=fgrs("0123456789",&t);
  while( t-- ) putchar(*p++);
  p=fgrs("1111",&t);
  while( t-- ) putchar(*p++);
  p=fgrs("11111",&t);
  while( t-- ) putchar(*p++);
0
user411313

これは問題に対するより一般的な解決策です。これは、シーケンス内のサブシーケンスの繰り返しを検出します。サブシーケンスは、最初から開始する必要も、互いにすぐ後に続く必要もありません。

問題のデータを含むシーケンスb [0..n]と、検出する最小のサブシーケンス長であるしきい値tが与えられると、

l_max = 0, i_max = 0, j_max = 0;
for (i=0; i<n-(t*2);i++) {
  for (j=i+t;j<n-t; j++) {
    l=0;
    while (i+l<j && j+l<n && b[i+l] == b[j+l])
      l++;
    if (l>t) {
      print "Sequence of length " + l + " found at " + i + " and " + j);
      if (l>l_max) {
        l_max = l;
        i_max = i;
        j_max = j;
      }
    }
  }
}
if (l_max>t) {
  print "longest common subsequence found at " + i_max + " and " + j_max + " (" + l_max + " long)";
}

基本的に:

  1. データの先頭から開始し、末尾の2 * t以内まで反復します(2 * t未満のスペースに長さtの2つの異なるサブシーケンスを作成する方法はありません!)
  2. 2番目のサブシーケンスの場合は、最初のシーケンスが始まる場所から少なくともtバイトを開始します。
  3. 次に、検出されたサブシーケンスの長さを0にリセットし、i + lとj + lに共通の文字があるかどうかを確認します。行う限り、lを増やします。共通のキャラクターがなくなったら、共通のサブシーケンスの終わりに到達しました。サブシーケンスがしきい値より長い場合は、結果を印刷します。
0
Rogan Dawes

これはキューを使用して思いついた解決策であり、コードフォースの同様の問題のすべてのテストケースに合格しました。問題Noは745Aです。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    string s, s1, s2; cin >> s; queue<char> qu; qu.Push(s[0]); bool flag = true; int ind = -1;
    s1 = s.substr(0, s.size() / 2);
    s2 = s.substr(s.size() / 2);
    if(s1 == s2)
    {
        for(int i=0; i<s1.size(); i++)
        {
            s += s1[i];
        }
    }
    //cout << s1 << " " << s2 << " " << s << "\n";
    for(int i=1; i<s.size(); i++)
    {
        if(qu.front() == s[i]) {qu.pop();}
        qu.Push(s[i]);
    }
    int cycle = qu.size();

    /*queue<char> qu2 = qu; string str = "";
    while(!qu2.empty())
    {
        cout << qu2.front() << " ";
        str += qu2.front();
        qu2.pop();
    }*/


    while(!qu.empty())
    {
        if(s[++ind] != qu.front()) {flag = false; break;}
        qu.pop();
    }
    flag == true ? cout << cycle : cout << s.size();
    return 0;
}
0

これを自分で理解し、多くのコメントを付けて(C#で記述された)このためのコードをいくつか記述しました。これが誰かを助けることを願っています:

// Check whether the string contains a repeating sequence.
public static bool ContainsRepeatingSequence(string str)
{
    if (string.IsNullOrEmpty(str)) return false;

    for (int i=0; i<str.Length; i++)
    {
        // Every iteration, cut down the string from i to the end.
        string toCheck = str.Substring(i);

        // Set N equal to half the length of the substring. At most, we have to compare half the string to half the string. If the string length is odd, the last character will not be checked against, but it will be checked in the next iteration.
        int N = toCheck.Length / 2;

        // Check strings of all lengths from 1 to N against the subsequent string of length 1 to N.
        for (int j=1; j<=N; j++)
        {
            // Check from beginning to j-1, compare against j to j+j.
            if (toCheck.Substring(0, j) == toCheck.Substring(j, j)) return true;
        }
    }

    return false;
}

動作する理由が不明な場合は、遠慮なく質問してください。

0
Foofnar

「効率的に」を定義する方法がわからない。簡単/高速な実装では、Javaでこれを行うことができます。

    private static String findSequence(String text) {
        Pattern pattern = Pattern.compile("(.+?)\\1+");
        Matcher matcher = pattern.matcher(text);
        return matcher.matches() ? matcher.group(1) : null;
    }

最短の文字列(.+?)少なくとも1回繰り返す必要があります(\1+)入力テキスト全体と一致します。

0
user85421

配列を文字列オブジェクトに変換し、正規表現を使用します

0
manolowar