O(n)のサフィックス配列を使用した文字列の最小の辞書式回転


9

ACM 2003の問題を引用します。

長さn(1 <= n <= 100000)の文字列を考えます。その最小の辞書式回転を決定します。たとえば、文字列「alabala」のローテーションは次のとおりです。

アラバラ

ラバラ

アバラール

バラアラ

アラララブ

ラアラバ

あらばる

その中で最小のものは「aalabal」です。

解決策として-私はサフィックス配列を構築する必要があることを知っています-そして、O(n)でそれを行うことができるとしましょう。私の質問はまだ、どうすればO(n)の最小回転を見つけることができますか?(n =文字列の長さ)

私はこの問題に非常に興味がありますが、それでも解決策が得られません。具体的な実装ではなく、概念と問題の解決方法に興味があります。

注:最小回転とは、英語の辞書と同じ順序であることを意味します。dはwの前にあるため、「dwor」は「word」の前にあります。

編集:サフィックス配列の構築にはO(N)が必要です

最終編集:解決策を見つけたと思います!!! 2つの文字列をマージした場合はどうなりますか?文字列が "alabala"の場合、新しい文字列は "alabalaalabala"になり、これのサフィックス配列(O(2n)= O(n)内)を作成して最初のサフィックスを取得しますか?これは正しいと思います。どう思いますか?ありがとうございました!


「最小」をどのように定義しますか?使用されているメトリックは何ですか(多分それは明白ですが、私は専門家ではありません)?
Giorgio

メモありがとうございます!回転は、辞書式順序による回転の結果ではなく、最小(最小オフセット)である必要があると思いました。
Giorgio

私はまだ何かが足りません:suffix配列の構築とソートは複雑さに含まれていますか?配列を作成して並べ替えるには、O(n)以上の時間がかかると思います。
Giorgio

元の弦を2回繰り返すというアイデアは素晴らしいと思います!次に、O(2n)= O(n)でサフィックス配列を作成できます。しかし、最小値を見つけるためにそれをソートする必要はありませんか?これにはO(n)以上のものが必要ですよね?
ジョルジョ

@Giorgioよく、suffix配列自体はすでにソートされたサフィックスを保持します。もう1つの注意点は、少しずれているかもしれません-並べ替えられたオブジェクトに対するいくつかの仮定を使用して、o(n)でも並べ替えができることを忘れないでください(たとえば、基数の並べ替えをチェックしてください)
Tomy

回答:


5

長さNの文字列のすべての回転を構築する簡単なトリックは、文字列をそれ自体と連結することです。

次に、この2N長の文字列のすべてのN長の部分文字列は、元の文字列のローテーションです。

次に、「辞書式に最小の」サブストリングを見つけることは、O(N)ツリー構造で行われます。


0

suffix配列に含まれている情報は、O(n)に到達するのに十分ではないと確信していますが、最大でもO(n log n)に役立ちます。このサフィックスのファミリーを検討してください:

a
aba
abacaba
abacabadabacaba
abacabadabacabaeabacabadabacaba
...

次のサフィックスを作成するには、前のサフィックス(たとえばaba)を取得し、まだ使用されていない次の文字を追加してから、前のサフィックスを再度追加します(so aba-> aba c aba)。

次に、これらの文字列を考えます(強調のためにスペースが追加されていますが、文字列の一部ではありません)。

ad abacaba
bd abacaba
cd abacaba

これら3つの文字列の場合、suffix配列の先頭は次のようになります。

a
aba
abacaba
(other suffixes)

見覚えがある?もちろん、これらの文字列は、このサフィックス配列を作成するように調整されています。さて、最初の文字(a、bまたはc)に応じて、「正しい」インデックス(問題の解決策)は、上記のリストの最初、2番目、または3番目のサフィックスになります。

最初の文字の選択は、suffix配列にほとんど影響しません。特に、suffix配列の最初の3つのサフィックスの順序には影響しません。これは、サフィックス配列が非常に類似しているが、「正しい」インデックスが非常に異なるlog n文字列があることを意味します。

ハードプルーフはありませんが、これは、配列の最初の3つのインデックスに対応するローテーションを辞書式順序で比較する以外に選択肢がないことを強く示唆しています。つまり、少なくともO(n log n)このための時間(代替の最初の文字の数-この例では3-はlog nであり、2つの文字列を比較するとO(n)時間かかります)。

これはO(n)アルゴリズムの可能性を除外するものではありません。サフィックスの配列がこの実行時間を達成するのに役立つことを疑うだけです。


0

最小の回転は、suffix配列の一部のサフィックスで始まる回転です。サフィックスは、辞書式順序で並べられます。これはあなたに大きなジャンプスタートを与えます:

  • 接尾辞kで始まる回転が接尾辞k +1で始まる回転よりも小さくなるようなkを取得すると、完了です(最初の回転から開始)。
  • あなたの比較です行うことができ、「サフィックス始まる回転をKサフィックス始まる回転よりも小さいKサフィックスの長さを比較し、必要に応じて、互いに文字で一文字を比較することにより、O(1)で+1」。

編集:「1つの文字と別の文字」は常にそうであるとは限らず、複数の文字である可能性がありますが、全体として、検索プロセス全体でn個を超える文字を調べないため、O(n)です。

ショート証明:サフィックスするときは、文字だけを調べるのk +1が長いサフィックスよりもK、および接尾辞あれば、あなたのソリューションを停止したK +1は接尾辞よりも短くなっているのk(あなたが知っているサフィックスkのあなたが求めるものです)。したがって、文字を調べるのは、サフィックスの(長さ方向の)昇順のシーケンスにいる間だけです。余分な文字のみを検査するため、n文字を超える検査はできません。

EDIT2:このアルゴリズムは、「接尾辞配列に2つの隣接接尾辞があり、前のものが後続のものよりも短い場合、前のものが後続の接頭辞である」という事実に依存しています。これが真実でない場合は、ごめんなさい。

EDIT3:いいえ、それは保持されません。「abaaa」には、「a」、「aa」、「aaa」、「abaaa」、「baaa」というサフィックステーブルがあります。しかし、おそらくこの考え方が最終的には解決策につながる可能性があります。詳細をさらに洗練させる必要があるだけです。主な質問は、前述の比較を少しの文字数で調べることができるかどうかです。そのため、完全にO(n)であり、何とか可能であると私は信じています。今はどうしてかわからない。


0

問題:

辞書式で最小の円形部分文字列は、そのようなすべての回転の辞書式順序が最も低い文字列の回転を見つける問題です。たとえば、「bbaaccaadd」の辞書式最小回転は「aaccaaddbb」になります。

解決:

AO(n)時間アルゴリズムは、Jean Pierre Duval(1983)によって提案されました。

2つのインデックスiとを指定するとj、Duvalのアルゴリズムj - iiandで始まる長さのストリングセグメントを比較しますj"duel"と呼ばれます)。index + j - iが文字列の長さより大きい場合、セグメントは折り返して形成されます。

たとえば、s = "baabbaba"、i = 5、j = 7について考えます。j-i = 2なので、i = 5で始まる最初のセグメントは "ab"です。j = 7で始まる2番目のセグメントは、ラップアラウンドによって構築され、これも「ab」です。上記の例のように、文字列が辞書式に等しい場合、勝者としてiから始まるものを選択します。つまり、i = 5です。

上記のプロセスは、勝者が1人になるまで繰り返されました。入力文字列の長さが奇数の場合、最後の文字が最初の反復での比較なしで勝ちます。

時間の複雑さ:

最初の反復では、長さ1のn個の文字列を比較し(n / 2比較)、2番目の反復では、長さ2のn / 2個の文字列を比較します(n / 2比較)。長さn / 2(n / 2の比較)。勝者の数は毎回半分になるため、再帰ツリーの高さはlog(n)であり、O(n log(n))アルゴリズムが得られます。小さなnの場合、これはおよそO(n)です。

スペースの複雑さもO(n)です。最初の反復では、n / 2の勝者、2番目の反復ではn / 4の勝者などを格納する必要があるためです。(ウィキペディアでは、このアルゴリズムは一定のスペースを使用していると主張していますが、方法はわかりません)。

これがScalaの実装です。お好みのプログラミング言語に自由に変換してください。

def lexicographicallyMinRotation(s: String): String = {
 @tailrec
 def duel(winners: Seq[Int]): String = {
   if (winners.size == 1) s"${s.slice(winners.head, s.length)}${s.take(winners.head)}"
   else {
     val newWinners: Seq[Int] = winners
       .sliding(2, 2)
       .map {
         case Seq(x, y) =>
           val range = y - x
           Seq(x, y)
             .map { i =>
               val segment = if (s.isDefinedAt(i + range - 1)) s.slice(i, i + range)
               else s"${s.slice(i, s.length)}${s.take(s.length - i)}"
               (i, segment)
             }
             .reduce((a, b) => if (a._2 <= b._2) a else b)
             ._1
         case xs => xs.head
       }
       .toSeq
     duel(newWinners)
   }
 }

 duel(s.indices)
}

-1

O(N²)より良いものはありません。

N個の整数のリストがある場合は、O(N)比較で最小のものを選択できます。

ここに、サイズNのN個の文字列のリストがあります(それらを作成してもコストはかかりません。文字列は開始インデックスによって完全に決定されます)。O(N)比較で最小のものを選択できます。ただし、各比較はO(N)基本操作です。したがって、複雑度はO(N²)です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.