我的一位朋友今天在面试时被问到以下问题:软件开发人员的职位:
如果给出两个字符串s1
和s2
,您将如何检查s1
是{strong>已旋转版本的s2
?
示例:
如果s1 = "stackoverflow"
,则以下是其中一些轮播版本:
"tackoverflows"
"ackoverflowst"
"overflowstack"
"stackoverflwo"
不 旋转版本。
他给出的答案是:
取
s2
并找到最长的前缀,即s1
的子字符串,它将为您提供旋转点。找到该点后,请在此时中断s2
以获取s2a
和s2b
,然后检查是否concatenate(s2a,s2b) == s1
对我和我的朋友来说,这似乎是一个很好的解决方案。但面试官不这么认为。他要求一个更简单的解决方案。请告诉我你将如何在Java/C/C++
中执行此操作来帮助我?
提前致谢。
答案 0 :(得分:688)
首先确保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);
}
答案 1 :(得分:101)
肯定会有一个更好的答案,“好吧,我会问堆栈流社区,并且可能在5分钟内至少有4个非常好的答案”。大脑是好的,但是我会更加重视那些知道如何与他人合作以获得解决方案的人。
答案 2 :(得分:49)
另一个python示例(基于答案):
def isrotation(s1,s2):
return len(s1)==len(s2) and s1 in 2*s2
答案 3 :(得分:32)
当其他人提交二次最坏情况时间复杂度解决方案时,我会添加一个线性的(基于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;
}
答案 4 :(得分:25)
我只是暴力逼迫它。首先检查长度,然后尝试每个可能的旋转偏移。如果它们都没有成功,则返回false - 如果有的话,立即返回true。
没有特别需要连接 - 只需使用指针(C)或索引(Java)并同时走,每个字符串中一个 - 从一个字符串的开头开始,第二个字符串中的当前候选旋转偏移量,以及必要时包装。检查字符串中每个点的字符相等性。如果你到达第一个字符串的末尾,那就完成了。
它可能很容易连接 - 尽管可能效率较低,至少在Java中是这样。
答案 5 :(得分:17)
这是一个使用正则表达式只是为了好玩:
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");
}
您也可以使用有限重复的lookbehind:
boolean isRotation(String s1, String s2) {
return (s1 + s2).matches(
String.format("(.*)(.*)(?<=^.{%d})\\2\\1", s1.length())
);
}
答案 6 :(得分:10)
哇,哇......为什么每个人都对O(n^2)
答案感到非常激动?我很肯定我们可以在这里做得更好。上面的答案包括O(n)
循环中的O(n)
操作(substring / indexOf调用)。即使使用更有效的搜索算法;说Boyer-Moore
或KMP
,最糟糕的情况仍然是O(n^2)
重复。
O(n)
随机答案很简单;采用支持O(1)
滑动窗口的哈希(如拉宾指纹);哈希字符串1,然后哈希字符串2,继续围绕字符串移动哈希1的窗口,看看哈希函数是否发生冲突。
如果我们想象最糟糕的情况就像是“扫描两股DNA”,那么碰撞的概率会上升,这可能会退化为类似O(n^(1+e))
或其他东西(只是在这里猜测)。
最后,有一个确定性的O(nlogn)
解决方案在外面有一个非常大的常数。基本上,这个想法是对两个字符串进行卷积。卷积的最大值将是旋转差(如果它们被旋转); O(n)
支票确认。好的是,如果有两个相等的最大值,那么它们都是有效的解决方案。你可以用两个FFT和一个点积和一个iFFT进行卷积,所以nlogn + nlogn + n + nlogn + n == O(nlogn)
。
由于你不能用零填充,并且你不能保证字符串长度为2 ^ n,因此FFT不会是快速的;它们将是慢速的,仍然是O(nlogn)
但是比CT算法更大的常数。
所有这一切,我绝对是100%肯定的,这里有一个确定性的O(n)
解决方案,但是如果我能找到它就会变得愚蠢。
答案 7 :(得分:8)
拳击,确保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);
}
答案 8 :(得分:8)
这是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;
}
答案 9 :(得分:7)
我认为最好在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;
}
答案 10 :(得分:6)
将每个字符作为幅度并对它们执行离散傅立叶变换。如果它们仅通过旋转而不同,则频谱将与舍入误差内的频率相同。当然这是低效的,除非长度是2的幂,所以你可以做FFT: - )
答案 11 :(得分:6)
不确定这是否是最有效的方法,但它可能相对有趣:Burrows-Wheeler transform。根据WP的文章,输入的所有旋转产生相同的输出。对于诸如压缩之类的应用,这是不可取的,因此指示原始旋转(例如通过索引;参见文章)。但对于简单的旋转独立比较,它听起来很理想。当然,它不一定非常有效!
答案 12 :(得分:5)
还没有人提供模数方法,所以这里有一个:
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注意到我上面代码中的缺陷。当字符串中的第一个字符出现两次或更多时,它会出错。例如,针对stackoverflow
进行测试的owstackoverflow
会导致错误,但应该是真的。
感谢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
这是lambda方法:
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
这是lambda方法输出:
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
答案 13 :(得分:3)
因为没有人提供过C ++解决方案。它在这里:
bool isRotation(string s1,string s2) {
string temp = s1;
temp += s1;
return (s1.length() == s2.length()) && (temp.find(s2) != string::npos);
}
答案 14 :(得分:2)
现在有一些完全不同的东西。
如果你想在某些受限制的上下文中获得非常快速的答案,那么当字符串不彼此旋转时
同意,它可能会失败,但是非常快速地说,如果字符串不匹配,如果它们匹配,你仍然可以使用字符串连接等其他算法来检查。
答案 15 :(得分:2)
C#:
s1 == null && s2 == null || s1.Length == s2.Length && (s1 + s1).Contains(s2)
答案 16 :(得分:2)
纯Java答案(无空检查)
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;
}
答案 17 :(得分:2)
我喜欢这个答案,检查s2是否是与s1连接的s1的子串。
我想添加一个不失优雅的优化。
不是连接字符串,而是可以使用连接视图(我不知道其他语言,但是对于C ++ Boost.Range提供了这样的视图)。
检查字符串是否是另一个字符串的子字符串具有线性平均复杂度(最坏情况复杂度是二次方),此优化应该将速度平均提高2倍。
答案 18 :(得分:2)
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()的实现是特定于平台的,但如果特别是脑死亡,可以总是用更快的替代方案替换,例如Boyer-Moore算法)
答案 19 :(得分:1)
另一个基于the回答的Ruby解决方案:
def rotation?(a, b); a.size == b.size and (b*2)[a]; end
答案 20 :(得分:1)
使用strlen
和strpos
函数在PHP中编写非常简单:
function isRotation($string1, $string2) {
return strlen($string1) == strlen($string2) && (($string1.$string1).strpos($string2) != -1);
}
我不知道strpos
在内部使用了什么,但如果它使用KMP,那么这将是线性的。
答案 21 :(得分:1)
反转其中一个字符串。采用两者的FFT(将它们视为简单的整数序列)。将结果逐点相乘。使用逆FFT变换回来。如果字符串是彼此的旋转,则结果将具有单个峰值 - 峰值的位置将指示它们相对于彼此旋转的程度。
答案 22 :(得分:0)
为什么不是这样的?
//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;
}
编辑:我可能会遇到一些问题;不想检查。 ;)
答案 23 :(得分:0)
我会在 Perl :
中执行此操作sub isRotation {
return length $_[0] == length $_[1] and index($_[1],$_[0],$_[0]) != -1;
}
答案 24 :(得分:0)
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;
}
答案 25 :(得分:0)
使用string1
加入string2
并使用KMP algorithm检查新形成的字符串中是否存在string2
。因为KMP的时间复杂度小于substr
。