今日、私の友人がインタビューでソフトウェア開発者の地位について次の質問をされました。
2つの文字列s1
とs2
が与えられた場合、s1
がrotatedバージョンのs2
であるかどうかをどのように確認しますか?
例:
s1 = "stackoverflow"
の場合、次はその回転バージョンの一部です。
"tackoverflows"
"ackoverflowst"
"overflowstack"
ここで"stackoverflwo"
はnotローテーションバージョンです。
彼が答えたのは:
s2
を取得し、s1
のサブ文字列である最も長いプレフィックスを見つけます。これにより、回転のポイントがわかります。そのポイントを見つけたら、そのポイントでs2
をブレークしてs2a
とs2b
を取得し、その後concatenate(s2a,s2b) == s1
をチェックするだけです。
それは私と私の友人にとって良い解決策のようです。しかし、インタビュアーはそうではないと考えました。彼はもっと簡単な解決策を求めました。 Java/C/C++
でこれを行う方法を教えてください。
前もって感謝します。
まず、s1
とs2
の長さが同じであることを確認してください。次に、s2
がs1
に連結されたs1
のサブストリングであるかどうかを確認します。
algorithm checkRotation(string s1, string s2)
if( len(s1) != len(s2))
return false
if( substring(s2,concat(s1,s1))
return true
return false
end
Javaの場合:
boolean isRotation(String s1,String s2) {
return (s1.length() == s2.length()) && ((s1+s1).indexOf(s2) != -1);
}
確かにより良い答えは、「まあ、私はstackoverflowコミュニティに尋ねると、おそらく5分以内に少なくとも4つの本当に良い答えがあるでしょう」でしょう。脳はすべて良好ですが、他の人と協力して解決策を得る方法を知っている人に高い価値を置きます。
別のpythonの例(答えに基づく):
def isrotation(s1,s2):
return len(s1)==len(s2) and s1 in 2*s2
他の人が二次最悪ケースの時間の複雑さのソリューションを提出したので、線形ソリューションを追加します( KMP Algorithm ):
bool is_rotation(const string& str1, const string& str2)
{
if(str1.size()!=str2.size())
return false;
vector<size_t> prefixes(str1.size(), 0);
for(size_t i=1, j=0; i<str1.size(); i++) {
while(j>0 && str1[i]!=str1[j])
j=prefixes[j-1];
if(str1[i]==str1[j]) j++;
prefixes[i]=j;
}
size_t i=0, j=0;
for(; i<str2.size(); i++) {
while(j>0 && str2[i]!=str1[j])
j=prefixes[j-1];
if(str2[i]==str1[j]) j++;
}
for(i=0; i<str2.size(); i++) {
if(j>=str1.size()) return true;
while(j>0 && str2[i]!=str1[j])
j=prefixes[j-1];
if(str2[i]==str1[j]) j++;
}
return false;
}
編集:あなたがそれを見つけた場合、受け入れられた答えは明らかにこれよりもエレガントで効率的です。元の文字列を2倍にすることを考えていなかった場合に、私はこの答えを残しました。
私はそれを総当たり攻撃したいと思います。最初に長さを確認してから、考えられるすべての回転オフセットを試します。どれもうまくいかない場合はfalseを返します-どれかがうまくいく場合は、すぐにtrueを返します。
連結する必要は特にありません-ポインター(C)またはインデックス(Java)を使用して、両方の文字列に沿って歩きます-1つの文字列の先頭から開始し、現在の候補回転オフセットを2番目の文字列に配置し、必要に応じてラッピングします。文字列の各ポイントで文字が等しいかどうかを確認します。最初の文字列の最後に到達したら、完了です。
おそらく、Javaの場合はそれほど効率的ではありませんが、連結するのはおそらく簡単でしょう。
正規表現を楽しみのために使用しているものを次に示します。
boolean isRotation(String s1, String s2) {
return (s1.length() == s2.length()) && (s1 + s2).matches("(.*)(.*)\\2\\1");
}
どちらの文字列にも含まれないことが保証されている特別な区切り文字を使用できる場合は、少し単純にすることができます。
boolean isRotation(String s1, String s2) {
// neither string can contain "="
return (s1 + "=" + s2).matches("(.*)(.*)=\\2\\1");
}
代わりに、有限の繰り返しで後読みを使用することもできます。
boolean isRotation(String s1, String s2) {
return (s1 + s2).matches(
String.format("(.*)(.*)(?<=^.{%d})\\2\\1", s1.length())
);
}
おっ、おっ...誰もがO(n^2)
の答えに興奮しているのはなぜですか?ここでもっとうまくやれると確信しています。上記の答えには、O(n)
ループ内のO(n)
操作(substring/indexOf呼び出し)が含まれています。より効率的な検索アルゴリズムでも; Boyer-Moore
またはKMP
を言うと、最悪の場合はまだ重複したO(n^2)
です。
O(n)
ランダム化された答えは簡単です。 O(1)
スライディングウィンドウをサポートするハッシュ(Rabin指紋のような)を取得します。ハッシュ文字列1、次にハッシュ文字列2、そしてハッシュ1のウィンドウを文字列の周りに移動し、ハッシュ関数が衝突するかどうかを確認します。
最悪のケースが「2本のDNAをスキャンする」ようなものだと想像すると、衝突の可能性が高くなり、おそらくO(n^(1+e))
などのようなものになります(ここで推測するだけです)。
最後に、外部に非常に大きな定数を持つ決定論的なO(nlogn)
ソリューションがあります。基本的には、2つの文字列の畳み込みをとることです。畳み込みの最大値は、回転差です(回転した場合)。 O(n)
チェックで確認します。良いことは、2つの等しい最大値がある場合、両方とも有効なソリューションであるということです。たたみ込みは、2つのFFTとドット積、およびiFFTを使用して実行できるため、nlogn + nlogn + n + nlogn + n == O(nlogn)
です。
ゼロを埋めることができず、文字列の長さが2 ^ nであることを保証できないため、FFTは高速のものではありません。それらは遅いもので、まだO(nlogn)
ですが、CTアルゴリズムよりもずっと大きな定数です。
とはいえ、ここには決定的なO(n)
ソリューションが存在することは間違いなく100%肯定的ですが、それを見つけることができれば、あえてします。
まず、2つの文字列の長さが同じであることを確認してください。次に、Cでは、単純なポインターの反復でこれを行うことができます。
int is_rotation(char* s1, char* s2)
{
char *tmp1;
char *tmp2;
char *ref2;
assert(s1 && s2);
if ((s1 == s2) || (strcmp(s1, s2) == 0))
return (1);
if (strlen(s1) != strlen(s2))
return (0);
while (*s2)
{
tmp1 = s1;
if ((ref2 = strchr(s2, *s1)) == NULL)
return (0);
tmp2 = ref2;
while (*tmp1 && (*tmp1 == *tmp2))
{
++tmp1;
++tmp2;
if (*tmp2 == '\0')
tmp2 = s2;
}
if (*tmp1 == '\0')
return (1);
else
++s2;
}
return (0);
}
以下はO(n)
であり、alghoritmです。文字列の要素に<
演算子を使用します。もちろん私のものではありません。 here から取得しました(このサイトは洗練されています。過去に一度つまずいたことがありますが、今では英語でそのようなものを見つけることができなかったので、自分の持っているものを示します:))。
bool equiv_cyc(const string &u, const string &v)
{
int n = u.length(), i = -1, j = -1, k;
if (n != v.length()) return false;
while( i<n-1 && j<n-1 )
{
k = 1;
while(k<=n && u[(i+k)%n]==v[(j+k)%n]) k++;
if (k>n) return true;
if (u[(i+k)%n] > v[(j+k)%n]) i += k; else j += k;
}
return false;
}
Java
でこれを行う方が良いと思います:
boolean isRotation(String s1,String s2) {
return (s1.length() == s2.length()) && (s1+s1).contains(s2);
}
Perlでは、次のことを行います。
sub isRotation {
my($string1,$string2) = @_;
return length($string1) == length($string2) && ($string1.$string1)=~/$string2/;
}
または、正規表現の代わりに index 関数を使用することもできます。
sub isRotation {
my($string1,$string2) = @_;
return length($string1) == length($string2) && index($string2,$string1.$string1) != -1;
}
各文字を振幅として取得し、それらに対して離散フーリエ変換を実行します。回転のみが異なる場合、周波数スペクトルは丸め誤差内と同じになります。もちろん、長さが2のべき乗でない限り、これは非効率的であるため、FFTを実行できます:-)
これが最も効率的な方法であるかどうかはわかりませんが、比較的興味深い: Burrows-Wheeler変換 かもしれません。 WPの記事によると、入力のすべての回転は同じ出力を生成します。圧縮などのアプリケーションでは、これは望ましくないため、元の回転が示されます(インデックスなど。記事を参照)。しかし、単純な回転に依存しない比較のためには、理想的です。もちろん、必ずしも理想的には効率的ではありません!
モジュロアプローチをまだ提供していないので、ここに1つあります。
static void Main(string[] args)
{
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "ztackoverflow"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "ackoverflowst"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "overflowstack"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "stackoverflwo"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "tackoverflwos"));
Console.ReadLine();
}
public static bool IsRotation(string a, string b)
{
Console.WriteLine("\nA: {0} B: {1}", a, b);
if (b.Length != a.Length)
return false;
int ndx = a.IndexOf(b[0]);
bool isRotation = true;
Console.WriteLine("Ndx: {0}", ndx);
if (ndx == -1) return false;
for (int i = 0; i < b.Length; ++i)
{
int rotatedNdx = (i + ndx) % b.Length;
char rotatedA = a[rotatedNdx];
Console.WriteLine( "B: {0} A[{1}]: {2}", b[i], rotatedNdx, rotatedA );
if (b[i] != rotatedA)
{
isRotation = false;
// break; uncomment this when you remove the Console.WriteLine
}
}
return isRotation;
}
出力:
A: stackoverflow B: ztackoverflow
Ndx: -1
Rotation : False
A: stackoverflow B: ackoverflowst
Ndx: 2
B: a A[2]: a
B: c A[3]: c
B: k A[4]: k
B: o A[5]: o
B: v A[6]: v
B: e A[7]: e
B: r A[8]: r
B: f A[9]: f
B: l A[10]: l
B: o A[11]: o
B: w A[12]: w
B: s A[0]: s
B: t A[1]: t
Rotation : True
A: stackoverflow B: overflowstack
Ndx: 5
B: o A[5]: o
B: v A[6]: v
B: e A[7]: e
B: r A[8]: r
B: f A[9]: f
B: l A[10]: l
B: o A[11]: o
B: w A[12]: w
B: s A[0]: s
B: t A[1]: t
B: a A[2]: a
B: c A[3]: c
B: k A[4]: k
Rotation : True
A: stackoverflow B: stackoverflwo
Ndx: 0
B: s A[0]: s
B: t A[1]: t
B: a A[2]: a
B: c A[3]: c
B: k A[4]: k
B: o A[5]: o
B: v A[6]: v
B: e A[7]: e
B: r A[8]: r
B: f A[9]: f
B: l A[10]: l
B: w A[11]: o
B: o A[12]: w
Rotation : False
A: stackoverflow B: tackoverflwos
Ndx: 1
B: t A[1]: t
B: a A[2]: a
B: c A[3]: c
B: k A[4]: k
B: o A[5]: o
B: v A[6]: v
B: e A[7]: e
B: r A[8]: r
B: f A[9]: f
B: l A[10]: l
B: w A[11]: o
B: o A[12]: w
B: s A[0]: s
Rotation : False
[編集:2010-04-12]
piotr 上記の私のコードの欠陥に気付きました。文字列の最初の文字が2回以上発生するとエラーになります。たとえば、stackoverflow
に対してテストされたowstackoverflow
は、trueであるべきときにfalseになりました。
エラーを発見してくれたpiotrに感謝します。
次に、修正したコードを示します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace TestRotate
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "ztackoverflow"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "ackoverflowst"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "overflowstack"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "stackoverflwo"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "tackoverflwos"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "owstackoverfl"));
Console.ReadLine();
}
public static bool IsRotation(string a, string b)
{
Console.WriteLine("\nA: {0} B: {1}", a, b);
if (b.Length != a.Length)
return false;
if (a.IndexOf(b[0]) == -1 )
return false;
foreach (int ndx in IndexList(a, b[0]))
{
bool isRotation = true;
Console.WriteLine("Ndx: {0}", ndx);
for (int i = 0; i < b.Length; ++i)
{
int rotatedNdx = (i + ndx) % b.Length;
char rotatedA = a[rotatedNdx];
Console.WriteLine("B: {0} A[{1}]: {2}", b[i], rotatedNdx, rotatedA);
if (b[i] != rotatedA)
{
isRotation = false;
break;
}
}
if (isRotation)
return true;
}
return false;
}
public static IEnumerable<int> IndexList(string src, char c)
{
for (int i = 0; i < src.Length; ++i)
if (src[i] == c)
yield return i;
}
}//class Program
}//namespace TestRotate
出力は次のとおりです。
A: stackoverflow B: ztackoverflow
Rotation : False
A: stackoverflow B: ackoverflowst
Ndx: 2
B: a A[2]: a
B: c A[3]: c
B: k A[4]: k
B: o A[5]: o
B: v A[6]: v
B: e A[7]: e
B: r A[8]: r
B: f A[9]: f
B: l A[10]: l
B: o A[11]: o
B: w A[12]: w
B: s A[0]: s
B: t A[1]: t
Rotation : True
A: stackoverflow B: overflowstack
Ndx: 5
B: o A[5]: o
B: v A[6]: v
B: e A[7]: e
B: r A[8]: r
B: f A[9]: f
B: l A[10]: l
B: o A[11]: o
B: w A[12]: w
B: s A[0]: s
B: t A[1]: t
B: a A[2]: a
B: c A[3]: c
B: k A[4]: k
Rotation : True
A: stackoverflow B: stackoverflwo
Ndx: 0
B: s A[0]: s
B: t A[1]: t
B: a A[2]: a
B: c A[3]: c
B: k A[4]: k
B: o A[5]: o
B: v A[6]: v
B: e A[7]: e
B: r A[8]: r
B: f A[9]: f
B: l A[10]: l
B: w A[11]: o
Rotation : False
A: stackoverflow B: tackoverflwos
Ndx: 1
B: t A[1]: t
B: a A[2]: a
B: c A[3]: c
B: k A[4]: k
B: o A[5]: o
B: v A[6]: v
B: e A[7]: e
B: r A[8]: r
B: f A[9]: f
B: l A[10]: l
B: w A[11]: o
Rotation : False
A: stackoverflow B: owstackoverfl
Ndx: 5
B: o A[5]: o
B: w A[6]: v
Ndx: 11
B: o A[11]: o
B: w A[12]: w
B: s A[0]: s
B: t A[1]: t
B: a A[2]: a
B: c A[3]: c
B: k A[4]: k
B: o A[5]: o
B: v A[6]: v
B: e A[7]: e
B: r A[8]: r
B: f A[9]: f
B: l A[10]: l
Rotation : True
ラムダアプローチは次のとおりです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace IsRotation
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "ztackoverflow"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "ackoverflowst"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "overflowstack"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "stackoverflwo"));
Console.WriteLine("Rotation : {0}",
IsRotation("stackoverflow", "owstackoverfl"));
string strToTestFrom = "stackoverflow";
foreach(string s in StringRotations(strToTestFrom))
{
Console.WriteLine("is {0} rotation of {1} ? {2}",
s, strToTestFrom,
IsRotation(strToTestFrom, s) );
}
Console.ReadLine();
}
public static IEnumerable<string> StringRotations(string src)
{
for (int i = 0; i < src.Length; ++i)
{
var sb = new StringBuilder();
for (int x = 0; x < src.Length; ++x)
sb.Append(src[(i + x) % src.Length]);
yield return sb.ToString();
}
}
public static bool IsRotation(string a, string b)
{
if (b.Length != a.Length || a.IndexOf(b[0]) < 0 ) return false;
foreach(int ndx in IndexList(a, b[0]))
{
int i = ndx;
if (b.ToCharArray().All(x => x == a[i++ % a.Length]))
return true;
}
return false;
}
public static IEnumerable<int> IndexList(string src, char c)
{
for (int i = 0; i < src.Length; ++i)
if (src[i] == c)
yield return i;
}
}//class Program
}//namespace IsRotation
ラムダアプローチの出力は次のとおりです。
Rotation : False
Rotation : True
Rotation : True
Rotation : False
Rotation : True
is stackoverflow rotation of stackoverflow ? True
is tackoverflows rotation of stackoverflow ? True
is ackoverflowst rotation of stackoverflow ? True
is ckoverflowsta rotation of stackoverflow ? True
is koverflowstac rotation of stackoverflow ? True
is overflowstack rotation of stackoverflow ? True
is verflowstacko rotation of stackoverflow ? True
is erflowstackov rotation of stackoverflow ? True
is rflowstackove rotation of stackoverflow ? True
is flowstackover rotation of stackoverflow ? True
is lowstackoverf rotation of stackoverflow ? True
is owstackoverfl rotation of stackoverflow ? True
is wstackoverflo rotation of stackoverflow ? True
誰もC++ソリューションを提供していないため。ここにそれ:
bool isRotation(string s1,string s2) {
string temp = s1;
temp += s1;
return (s1.length() == s2.length()) && (temp.find(s2) != string::npos);
}
純粋なJava回答(nullチェックなし)
private boolean isRotation(String s1,String s2){
if(s1.length() != s2.length()) return false;
for(int i=0; i < s1.length()-1; i++){
s1 = new StringBuilder(s1.substring(1)).append(s1.charAt(0)).toString();
//--or-- s1 = s1.substring(1) + s1.charAt(0)
if(s1.equals(s2)) return true;
}
return false;
}
C#:
s1 == null && s2 == null || s1.Length == s2.Length && (s1 + s1).Contains(s2)
Operaのシンプルなポインター回転のトリックは機能しますが、実行時間の最悪の場合には非常に非効率的です。単純に、多くの長い文字が繰り返し実行される文字列を想像してください。
S1 = HELLOHELLOHELLO1HELLOHELLOHELLO2
S2 = HELLOHELLOHELLO2HELLOHELLOHELLO1
「不一致が発生するまでループしてから、1ずつ増やして再試行する」というのは計算上恐ろしいアプローチです。
プレーンなCであまり手間をかけずに連結アプローチを実行できることを証明するために、ここに私の解決策があります。
int isRotation(const char* s1, const char* s2) {
assert(s1 && s2);
size_t s1Len = strlen(s1);
if (s1Len != strlen(s2)) return 0;
char s1SelfConcat[ 2 * s1Len + 1 ];
sprintf(s1SelfConcat, "%s%s", s1, s1);
return (strstr(s1SelfConcat, s2) ? 1 : 0);
}
これは、O(n)メモリ使用量のオーバーヘッドを犠牲にして、実行時間に比例します。
(strstr()の実装はプラットフォーム固有ですが、特に脳死の場合は、ボイヤー・ムーアアルゴリズムなどのより高速な代替品にいつでも置き換えることができます)
そして今、完全に異なる何かのために。
文字列がnot相互の回転である場合、いくつかの制約されたコンテキストで非常に高速な回答が必要な場合
同意しますが、失敗する可能性がありますが、文字列が一致しない場合はvery高速であり、一致する場合は文字列連結などの別のアルゴリズムを使用して確認できます。
私は、s2がs1と連結されたs1の部分文字列であるかどうかをチェックする答えが好きです。
エレガントさを失わない最適化を追加したかった。
文字列を連結する代わりに、結合ビューを使用できます(他の言語については知りませんが、C++ Boost.Rangeにはそのようなビューが用意されています)。
文字列が他の文字列の部分文字列であるかどうかのチェックには線形平均複雑度(最悪の場合の複雑度は2次関数)があるため、この最適化により速度が平均2倍向上します。
文字列の1つを逆にします。両方のFFTを取得します(それらを整数の単純なシーケンスとして扱います)。結果をポイントごとに乗算します。逆FFTを使用して逆変換します。文字列が互いに回転している場合、結果には単一のピークがあります。ピークの位置は、文字列が互いに対してどれだけ回転しているかを示します。
strlen
およびstrpos
関数を使用してPHPに記述するのは非常に簡単です。
function isRotation($string1, $string2) {
return strlen($string1) == strlen($string2) && (($string1.$string1).strpos($string2) != -1);
}
strpos
が内部的に使用するものはわかりませんが、 KMP を使用する場合、これは時間的に線形になります。
the に基づく別のRubyソリューション:
def rotation?(a, b); a.size == b.size and (b*2)[a]; end
このようなものはなぜですか?
//is q a rotation of p?
bool isRotation(string p, string q) {
string table = q + q;
return table.IndexOf(p) != -1;
}
もちろん、独自のIndexOf()関数を書くこともできます。 .NETが単純な方法を使用するか、より高速な方法を使用するかはわかりません。
ナイーブ:
int IndexOf(string s) {
for (int i = 0; i < this.Length - s.Length; i++)
if (this.Substring(i, s.Length) == s) return i;
return -1;
}
もっと早く:
int IndexOf(string s) {
int count = 0;
for (int i = 0; i < this.Length; i++) {
if (this[i] == s[count])
count++;
else
count = 0;
if (count == s.Length)
return i - s.Length;
}
return -1;
}
編集:私はいくつかのオフバイワンの問題があるかもしれません。チェックする気にならないでください。 ;)
Perlでこれを行います:
sub isRotation {
return length $_[0] == length $_[1] and index($_[1],$_[0],$_[0]) != -1;
}
string1
とstring2
を結合し、 KMPアルゴリズム を使用して、新しく形成された文字列にstring2
が存在するかどうかを確認します。 KMPの時間の複雑さはsubstr
よりも小さいためです。
int rotation(char *s1,char *s2)
{
int i,j,k,p=0,n;
n=strlen(s1);
k=strlen(s2);
if (n!=k)
return 0;
for (i=0;i<n;i++)
{
if (s1[0]==s2[i])
{
for (j=i,k=0;k<n;k++,j++)
{
if (s1[k]==s2[j])
p++;
if (j==n-1)
j=0;
}
}
}
if (n==p+1)
return 1;
else
return 0;
}