O(1)追加スペースを使用して、2つの文字列が互いの順列であるかどうかを確認する方法は?


13

2つの文字列が与えられた場合、O(1)スペースを使用して、それらが互いの順列であるかどうかをどのように確認できますか 文字列を変更することは一切許可されていません。
注:文字列の長さとアルファベットのサイズの両方に関連するO(1)スペース。


3
どう思いますか?何を試しましたか、どこで行き詰まりましたか?文字列は一定サイズのアルファベットを超えていますか?ヒストグラムを計算してみましたか?
ユヴァルフィルマス

@YuvalFilmus文字列の長さとアルファベットのサイズの両方に対してO(1)スペースである必要があります
匿名

これは明らかに不可能なようです。どのアルゴリズムでも、少なくとも1つの文字列または単一の文字の位置を格納するために追加のスペースが必要です。これらのいずれもO(1)ではありません。
デビッドシュワルツ

@DavidSchwartz-方法は?O(1)は定数であり、1つのビュートではありません。文字列の長さは関係なく、その位置は1つの数字です。
デイヴァー

それはマシンモデルに依存し、明らかに均一なモデルでは問題ありません。対数コストモデルでは、インデックスを格納O(log n)するのは、長さもアルファベットサイズも一定ではない長さnの文字列です。文字列を一時的に変更できる場合、アルファベットサイズは線形ですが、対数モデルでは文字列の長さは一定である、アルファベットが増加したソリューションがあると思います。
-kap

回答:


7

素朴なアプローチは、両方の文字列のヒストグラムを作成し、それらが同じかどうかを確認することです。1つのパスで計算できるようなデータ構造(サイズはアルファベットのサイズに線形になる)を保存することは許可されていないため、考えられる各シンボルの発生を次々にカウントする必要があります。

function count(letter, string)
    var count := 0
    foreach element in string
        if letter = element
            count++
    return count

function samePermutation(stringA, stringB)
    foreach s in alphabet
        if count(s, stringA) != count(s, stringB)
            return false
    return true

もちろん、これはカウントとイテレータインデックスが文字列の長さに依存するのではなく、一定サイズの整数であることを前提としています。


最適化として、1つの配列を調べて、発生した文字のヒストグラムのみを計算できます。このようにして、複雑さはアルファベットのサイズに依存しなくなります。
ユヴァルフィルマス

@YuvalFilmusコメントを展開するには、1)文字列の長さが同じであることを確認するか、2)両方の入力文字列を反復処理する必要があります。一方の文字が他方の文字にない可能性があるため、これらのいずれかが必要です。オプション1の計算は少なくする必要があります。
BurnsBA

@YuvalFilmus二次的な時間の複雑さを意味するので、アルファベットは平均的な文字列サイズよりも小さいことを避けたいと思いました。小さな文字列と順序付けられたアルファベットの場合、次の最小の現在のシンボルと内側ループのカウントを計算することを検討します。これにより、アルファベットループの数回の反復を-の複雑さでスキップできO(n * min(n, |Σ|))ます。ええと、今考えてみると、それはあなたの答えから「繰り返すことを許された」解決策のように聞こえますよね?
ベルギ

countないO(1)(つまり、オーバーフローする可能性があります)
-reinierpost

1
@Eternalcodeそれcountint:-)であると言ったことはありません。はい、動作しませんが、Javaではとにかく起こることができません
Bergi

12

配列を、長さがnであると仮定します。ABn

まず、各配列の値が異なると仮定します。空間を使用するアルゴリズムは次のとおりです。O(1)

  1. 両方の配列の最小値を計算し、それらが同一であることを確認します。

  2. 両方の配列の2番目の最小値を計算し、それらが同一であることを確認します。

  3. 等々。

配列の最小値を計算するには、明らかにスペースが使用されます。与えられたkの最小番目の要素、我々は見つけることができますK + 1 より大きく、最小値求めることでST最小の要素のk番目の最小の要素を(ここでは、我々はすべての要素が明瞭であるという事実を使用します)。O(1)k(k+1k

要素の繰り返しが許可されている場合、アルゴリズムを次のように変更します。

  1. 両方の配列の最小値を計算し、それぞれが出現する回数をカウントし、m A 1 = m B 1およびカウントが同一であることを確認します。mA,1mB1mA1=mB1

  2. 2つの配列内のm A 1m B 1より大きい最小値計算し(それぞれ)、それぞれの出現回数をカウントします。ことを確認し、M A 2 = M B 2、及び数が同一です。mA2mB2mA1mB1mA2=mB2

  3. 等々。


1
O 1 空間でmin要素を見つける唯一の方法であり、配列への読み取り専用アクセスはすべての要素を反復することであるため、このアプローチはでしょうか?On2O1
ライアン

4
これにはアルファベットの順序が必要ですが、それを必要としないようにアルゴリズムを変更するのは簡単です。しかし、ケース"重複を有する"で、これは必要空間ではなく、O 1 。カウントにはスペースが必要です。OlgnO1
デレクエルキンズは

7
カウントには(対数)スペースが必要ですが、このスペース使用量の定義により、配列の反復処理も必要です。したがって、スペース使用の厳密な意味では、一定のスペースでそれを行う方法はありません。
ダニエルジュール

4
@DanielJour、使用しているコストモデルによって異なります。均一なコストの下では、これは一定のスペースで可能です。
ライアン

7
一定のビット数しか許可されていない場合は、一定サイズのアルファベットしか処理できません(これは通常の言語の理論に基づいています)。
ユヴァルフィルマス

2

文字cを一意の素数にマップする関数f(c)を定義します(a = 2、b = 3、c = 5など)。

set checksum = 1
set count = 0 <-- this is probably not even necessary, but it's another level of check
for character c in string 1
    checksum = checksum * f(c)
    count = count + 1
for character c in string 2
    checksum = checksum / f(c)
    count = count = 1

permutation = count == 0 and checksum == 1

素数のマッピング関数を使用できることを宣言するのは少し手間がかかり、ほとんどの場合、スペースを保持しているときに問題が発生します。O(1)


アルファベットに制限がある場合、O 1 スペースを使用する必要があります。そうしないと、一定のスペースではないはずです。さらに、O 1 空間で計算した場合、現在の結果に基づいて非常に非効率になります。それでも、素数性アプローチでは+1。fcO1O1
ライアン

投稿後に気付いた別の問題は、チェックサムはそれ自体でO(1)スペース要件に違反する可能性がある範囲で、大きな文字列の巨大な数になることです。これは、フロートを使用して、ある文字列の文字で複数の文字を分割し、別の文字列で分割し、チェックサムが1 に近い必要があると言うことで解決できます。浮動小数点エラーが問題になるには、文字列が本当に巨大でなければなりません。
アレックススタッセ

4
このような答えは、計算モデルに注意する必要がある理由です。アルゴリズムを分析するときに使用する通常のモデルは、サイズがO log n ビットのマシンワードの単位でメモリをカウントします。したがって、整数で計算することはできません。浮動小数点に切り替えると、2つの文字列が相互に置換されている場合でもアルゴリズムが失敗する可能性があり、逆にそうでない場合は必ずしも正しい答えが得られません。Oログn
ユヴァルフィルマス

4
これは一定のスペースを使用しません。固定アルファベットの場合でも、整数チェックサムのサイズは、長さnの入力に対してビットになります。Θnn
デヴィッドリチャービー

0

これを行うことができますO(nlogn)。2つの文字列を並べ替え、それらをインデックスごとに比較します。どこでも異なる場合、それらは互いの順列ではありません。

以下のためにO(n)解決策、ハッシュを使用することができます。このハッシュ関数は機能し e、どの文字に対してもASCII値になります。文字列の2つのハッシュが異なる場合、それらは互いの順列ではありません。

リンク内のハッシュ関数:

1つの潜在的な候補はこれかもしれません。奇数の整数Rを修正します。ハッシュする各要素eについて、係数(R + 2 * e)を計算します。次に、これらすべての要因の積を計算します。最後に、製品を2で除算してハッシュを取得します。

(R + 2e)の係数2は、すべての係数が奇数であることを保証します。したがって、積が0になることを回避します。最後の2による除算は、積が常に奇数になるためです。 。

たとえば、R = 1779033703を選択します。これは任意の選択です。特定のRが良いか悪いかを示すいくつかの実験を行う必要があります。値が[1、10、3、18]であると仮定します。製品(32ビット整数を使用して計算)は

(R + 2)*(R + 20)*(R + 6)*(R + 36)= 3376724311したがって、ハッシュは

3376724311/2 = 1688362155。

Rの値を変更してダブルハッシュを使用する(またはさらに過剰に使用する)と、Rが非常に高い確率で順列として正常に識別されます。


1
文字列を変更することは許可されていないため、文字列を並べ替えることはできません。ハッシュに関しては、間違った答えを与える可能性があるランダム化されたアルゴリズムです。
ユヴァルフィルマス

0

sとtという2つの文字列があるとします。

ヒューリスティックを使用して、それらが等しくないことを確認できます。

  1. s.length == t.length
  2. sの文字の合計== tの文字の合計
  3. [2と同じですが、合計の代わりにxorを使用]

この後、文字列が等しいことを証明するアルゴリズムを簡単に実行できます。

  1. 1つの文字列を他の文字列と等しくなるように並べ替えて、(O(n ^ 2))を比較します
  2. 両方を並べ替えて比較する(O(2n log(n))
  3. 両方の文字列に同じ量がある場合、sの各文字をチェックします(O(n ^ 2))

もちろん、追加のスペースを使用することを許可されていない場合は、それほど速く並べ替えることはできません。したがって、どのアルゴリズムを選択しても問題ありません-O(1)スペースしかない場合、およびヒューリスティックが等しくないことを証明できない場合、各アルゴリズムはO(n ^ 2)時間で実行されます。


3
文字列を変更することは一切許可されません。
ベルギ

0

ルーチン全体のCスタイルコード:

for (int i = 0; i < n; i++) {
   int k = -1;
   next: for (int j = 0; j <= i; j++)
       if (A[j] == A[i]) {
          while (++k < n)
              if (B[k] == A[i])
                  continue next;
          return false; // note at this point j == i
       }
}
return true; 

または非常に詳細な擬似コード(1ベースのインデックス作成を使用)

// our loop invariant is that B contains a permutation of the letters
// in A[1]..A[i-1]
for i=1..n
   if !checkLetters(A, B, i)
      return false
return true

ここで、関数checkLetters(A、B、i)は、A [1] .. A [i]にA [i]のMコピーがある場合、Bに少なくともA [i]のMコピーがあることをチェックします。

checkLetters(A,B,i)
    k = 0 // scan index into B
    for j=1..i
      if A[j] = A[i]
         k = findNextValue(B, k+1, A[i])
         if k > n
            return false
    return true

関数findNextValueは、インデックスで始まる値をBで検索し、見つかったインデックス(または見つからない場合はn + 1)を返します。

n2


Cコードを擬似コードに変換していただけますか?これはプログラミングサイトではありません。
ユヴァルフィルマス

これは、ベルギの答えの別の変種のように思えます(いくつかの取るに足らない違いがあります)。
ユヴァルフィルマス

OnmOn2

0

On3n

ループを通るstring1string2、それはで見つけることができますどのように多くの場合、すべての文字チェックのためにstring1string2。文字は、他の文字列よりも1つの文字列に含まれていることが多いため、順列ではありません。すべての文字の頻度が等しい場合、文字列は互いの順列です。

これを正確にするためのpythonがあります

s1="abcaba"
s2="aadbba"

def check_if_permutations(string1, string2):
  for string in [string1, string2]:
    # string references string1 
    #  string2, it is not a copy
    for char in string:
      count1=0
      for char1 in string1:
        if  char==char1:
          count1+=1
      count2=0
      for char2 in string2:
        if  char==char2:
          count2+=1
      if count1!=count2:
        print('unbalanced character',char)
        return()
  print ("permutations")
  return()

check_if_permutations(s1,s2)

stringstring1string2charchar1char2Oログncount1count2string[string1, string2]

もちろん、カウント変数も必要ありませんが、ポインターを使用できます。

s1="abcaba"
s2="aadbba"

def check_if_permutations(string1, string2):
  for string in [string1, string2]:
    # string references one of string1 
    # or string2, it is not a copy
    for char in string:
      # p1 and p2 should be views as pointers
      p1=0
      p2=0
      while (p1<len(string1)) and (p2<len(string2)):
        # p1>=len(string1): p1 points to beyond end of string
        while (p1<len(string1)) and (string1[p1]!=char) :
          p1+=1
        while(p2<len(string2)) and (string2[p2]!=char):
          p2+=1
        if (p1<len(string1)) != (p2<len(string2)):
          print('unbalanced character',char)
          return()
        p1+=1
        p2+=1
  print ("permutations")
  return()

check_if_permutations(s1,s2)

Oログn

n


これは、以下のBergiのソリューションと同じです。
ユヴァルフィルマス

@YuvalFilmusいいえ、アルファベット全体を反復処理しないため、そのランタイムはアルファベットサイズに依存しません。テストする必要がある2つの文字列のみを使用します。また、2番目のプログラムはカウントを回避します。
miracle173

@YuvalFilmus私は今、あなたや他のコメントが私のプログラムで使用した方法の方向性を示していることを知っています。
miracle173
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.