Вопрос с интервью: проверьте, является ли одна строка поворот другой строки

Другу моему был задан следующий вопрос сегодня в интервью для должности разработчика программного обеспечения:

Учитывая две строки s1 и s2, как вы проверите, является ли s1 версией s2 <

Пример:

Если s1 = "stackoverflow", то некоторые из его повернутых версий:

"tackoverflows"
"ackoverflowst"
"overflowstack"

где "stackoverflwo" не развернутая версия.

Ответ, который он дал, был:

Возьмите s2 и найдите самый длинный префикс, который является подстрокой s1, которая даст вам точку поворота. Как только вы найдете эту точку, сломайте s2 в этой точке, чтобы получить s2a и s2b, а затем просто проверьте, если concatenate(s2a,s2b) == s1

Это похоже на хорошее решение для меня и моего друга. Но интервьюер думал иначе. Он попросил более простое решение. Пожалуйста, помогите мне, сообщив, как вы это сделаете в Java/C/C++?

Спасибо заранее.

Ответ 1

Сначала убедитесь, что 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);
}

Ответ 2

Разумеется, лучшим ответом будет "Ну, я бы спросил сообщество stackoverflow и, вероятно, имел бы по крайней мере 4 действительно хорошие ответы в течение 5 минут". Мозги хорошие и все, но я бы поставил более высокую ценность на тех, кто знает, как работать с другими, чтобы получить решение.

Ответ 3

Другой пример python (на основе ответа):

def isrotation(s1,s2):
     return len(s1)==len(s2) and s1 in 2*s2

Ответ 4

В то время как другие представили квадратичное решение для временной сложности времени, я бы добавил линейный (на основе 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;
}

рабочий пример

Ответ 5

EDIT: принятый ответ явно более изящный и эффективный, чем этот, если вы его заметили. Я оставил этот ответ как то, что я сделал бы, если бы не думал о удвоении исходной строки.


Я бы просто переборщил. Сначала проверьте длину, а затем попробуйте каждое возможное смещение вращения. Если ни один из них не работает, верните false - если это произойдет, немедленно верните true.

Нет особой необходимости в конкатенации - просто используйте указатели (C) или индексы (Java) и пройдите по одной, по одной в каждой строке - начиная с начала одной строки и текущего смещения вращения кандидата во второй строке и если необходимо. Проверьте соответствие символов в каждой точке строки. Если вы дойдете до конца первой строки, вы закончили.

Скорее всего, это будет так же легко конкатенировать - хотя, вероятно, менее эффективно, по крайней мере, на Java.

Ответ 6

Здесь используется регулярное выражение только для удовольствия:

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())
   );
}

Ответ 7

Whoa, whoa... почему все так взволнованы ответом O(n^2)? Я уверен, что здесь мы можем сделать лучше. Ответ выше включает операцию O(n) в цикле O(n) (вызов substring/indexOf). Даже с более эффективным алгоритмом поиска; скажем Boyer-Moore или KMP, худший вариант по-прежнему O(n^2) с дубликатами.

A O(n) рандомизированный ответ прост; возьмите хэш (например, отпечаток Rabin), который поддерживает скользящее окно O(1); хеш-строку 1, затем строку хеша 2, и перейдите к перемещению окна для хэш-1 вокруг строки и посмотрите, сталкиваются ли хеш-функции.

Если представить, что худший случай - это что-то вроде "сканирования двух нитей ДНК", то вероятность столкновения возрастает, и это, вероятно, вырождается до чего-то вроде O(n^(1+e)) или что-то (просто угадывая здесь).

Наконец, существует детерминированное решение O(nlogn), которое имеет очень большую постоянную вне. В принципе, идея состоит в том, чтобы взять свертку двух строк. Максимальное значение свертки будет разностью вращения (если они повернуты); проверяется проверка O(n). Приятно, что если есть два равных максимальных значения, то они оба являются также действительными решениями. Вы можете выполнить свертку с двумя БПФ и точечным продуктом и iFFT, поэтому nlogn + nlogn + n + nlogn + n == O(nlogn).

Поскольку вы не можете набивать нулями, и вы не можете гарантировать, что строки имеют длину 2 ^ n, БПФ не будут быстрыми; они будут медленными, все еще O(nlogn), но гораздо большей константой, чем алгоритм CT.

Все, что сказал, я абсолютно, на 100% уверен, что здесь есть детерминированное решение O(n), но штопает, если я могу его найти.

Ответ 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);
}

Ответ 9

Вот O(n) и на месте alghoritm. Он использует оператор < для элементов строк. Это не мое, конечно. Я взял его из здесь (сайт на польском языке. Я наткнулся на него один раз в прошлом, и я не мог найти что-то в этом роде сейчас на английском, поэтому я показываю, что у меня есть:)).

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;
}

Ответ 10

Я думаю, что лучше сделать это в 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;
}

Ответ 11

Не уверен, что это наиболее эффективный метод, но он может быть относительно интересным: преобразование Burrows-Wheeler. Согласно статье WP, все повороты ввода дают одинаковый выход. Для таких приложений, как сжатие, это нежелательно, поэтому указывается исходное вращение (например, индексом, см. Статью). Но для простого независимого от вращения сравнения это звучит идеально. Конечно, это не обязательно идеально эффективно!

Ответ 12

Возьмите каждый символ как амплитуду и выполните на них дискретное преобразование Фурье. Если они отличаются только вращением, частотные спектры будут одинаковыми с погрешностью округления. Конечно, это неэффективно, если длина не равна 2, поэтому вы можете делать БПФ: -)

Ответ 13

Никто еще не предложил модульный подход, так вот один из них:

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

[EDIT: 2010-04-12]

piotr заметил недостаток в моем коде выше. Это ошибки, когда первый символ в строке происходит дважды или более. Например, stackoverflow, протестированный против owstackoverflow, привел к 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

Ответ 14

Поскольку никто не дал решение на С++. вот он это:

bool isRotation(string s1,string s2) {

  string temp = s1;
  temp += s1;
  return (s1.length() == s2.length()) && (temp.find(s2) != string::npos);
}

Ответ 15

Операция простого простого поворота указателя работает, но она крайне неэффективна в худшем случае во время работы. Просто представьте себе строку со многими длинными повторяющимися прогонами символов, например:

S1 = HELLOHELLOHELLO1HELLOHELLOHELLO2

S2 = HELLOHELLOHELLO2HELLOHELLOHELLO1

Цикл "до тех пор, пока не произойдет рассогласование, а затем увеличится на единицу и повторите попытку" - это ужасный подход, вычисляемый.

Чтобы доказать, что вы можете сделать подход конкатенации в простой 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() является специфичной для платформы, но, если ее особенно убить мозг, всегда можно заменить более быстрой альтернативой, такой как алгоритм Бойера-Мура)

Ответ 16

С#:

s1 == null && s2 == null || s1.Length == s2.Length && (s1 + s1).Contains(s2)

Ответ 17

Мне нравится ответ, который проверяет, является ли s2 подстрокой s1, объединенной с s1.

Я хотел добавить оптимизацию, которая не теряет своей элегантности.

Вместо конкатенации строк вы можете использовать представление соединения (я не знаю другого языка, но для С++ Boost.Range предоставляют такие виды просмотров).

Поскольку проверка, является ли строка подстрокой другого, имеет линейную среднюю сложность (сложность наихудшего случая квадратична), эта оптимизация должна в среднем повысить скорость в среднем на 2 раза.

Ответ 18

Чистый ответ Java (sans null check)

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;
}

Ответ 19

И теперь для чего-то совершенно другого.

Если вам нужен очень быстрый ответ в ограниченном контексте, когда строки не вращаются друг от друга

  • вычислить некоторую символьную контрольную сумму (такую ​​как xoring все символы) на обеих строках. Если подписи различаются, то строки не являются вращениями друг друга.

Согласовано, оно может потерпеть неудачу, но очень быстро сказать, если строки не совпадают, и если они совпадают, вы можете использовать другой алгоритм, например, для конкатенации строк.

Ответ 20

Другое решение Ruby на основе :

def rotation?(a, b); a.size == b.size and (b*2)[a]; end

Ответ 21

Очень легко писать на PHP с помощью функций strlen и strpos:

function isRotation($string1, $string2) {
    return strlen($string1) == strlen($string2) && (($string1.$string1).strpos($string2) != -1);
}

Я не знаю, что strpos использует внутренне, но если он использует KMP, это будет линейным по времени.

Ответ 22

Отверните одну из строк. Возьмите БПФ обоих (рассматривая их как простые последовательности целых чисел). Умножьте результаты по точкам. Преобразуйте обратно с помощью обратного БПФ. Результат будет иметь один пик, если строки являются вращениями друг друга - положение пика будет указывать, насколько они вращаются относительно друг друга.

Ответ 23

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;
}

Ответ 24

Почему не что-то вроде этого?


//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;
}

Изменить: у меня могут быть проблемы с отдельными задачами; не хочется проверять.;)

Ответ 25

Я бы сделал это в Perl:

sub isRotation { 
     return length $_[0] == length $_[1] and index($_[1],$_[0],$_[0]) != -1; 
}

Ответ 26

Присоедините string1 к string2 и используйте алгоритм KMP, чтобы проверить, присутствует ли string2 во вновь сформированной строке. Поскольку временная сложность KMP меньше, чем substr.