Проверьте мой код анаграммы из прошлого собеседования

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

Учитывая список строк, верните список наборов строк, которые являются анаграммами входного набора. т.е. "собака", "бог", "фу" должны вернуть {"собака", "бог"}. Впоследствии я создал код самостоятельно для проверки работоспособности, и он уже некоторое время существует. Я хотел бы получить информацию об этом, чтобы увидеть, пропустил ли я что-нибудь или мог бы сделать это гораздо более эффективно. Воспользуйтесь возможностью улучшить себя и изучить другие техники:


void Anagram::doWork(list input, list> &output)
{
  typedef list < pair < string, string>> SortType;
  SortType sortedInput;
// sort each string and pair it with the original for(list< string >::iterator i = input.begin(); i != input.end(); ++i) { string tempString(*i); std::sort(tempString.begin(), tempString.end()); sortedInput.push_back(make_pair(*i, tempString)); }
// Now step through the new sorted list for(SortType::iterator i = sortedInput.begin(); i != sortedInput.end();) { set< string > newSet;
// Assume (hope) we have a match and pre-add the first. newSet.insert(i->first);
// Set the secondary iterator one past the outside to prevent // matching the original SortType::iterator j = i; ++j;
while(j != sortedInput.end()) { if(i->second == j->second) { // If the string matches, add it to the set and remove it // so that future searches need not worry about it newSet.insert(j->first); j = sortedInput.erase(j); } else { // else, next element ++j; } }
// If size is bigger than our original push, we have a match // - save it to the output if(newSet.size() > 1) { output.push_back(newSet); }
// erase this element and update the iterator i = sortedInput.erase(i); } }

Вот второй шаг после просмотра комментариев и получения дополнительной информации:


void doBetterWork(list input, list> &output)
{
  typedef std::multimap< string, string > SortedInputType;
  SortedInputType sortedInput;
  vector< string > sortedNames;
for(vector< string >::iterator i = input.begin(); i != input.end(); ++i) { string tempString(*i); std::sort(tempString.begin(), tempString.end()); sortedInput.insert(make_pair(tempString, *i)); sortedNames.push_back(tempString); }
for(list< string >::iterator i = sortedNames.begin(); i != sortedNames.end(); ++i) { pair< SortedInputType::iterator,SortedInputType::iterator > bounds; bounds = sortedInput.equal_range(*i);
set< string > newSet; for(SortedInputType::iterator j = bounds.first; j != bounds.second; ++j) { newSet.insert(j->second); }
if(newSet.size() > 1) { output.push_back(newSet); }
sortedInput.erase(bounds.first, bounds.second); } }


person Michael Dorgan    schedule 25.04.2010    source источник
comment
Не думаю, что код вставлен правильно.   -  person MK.    schedule 25.04.2010
comment
лично я считаю такой вопрос для собеседования сомнительным, люди обычно довольно напряжены на собеседовании и могут только доказать, хорошо ли человек думает на ногах под давлением (в этом случае), но на самом деле мало говорит о навыках программирования.   -  person AndersK    schedule 25.04.2010
comment
Наверное, но я так испугался, что вспомнил правильное форматирование глупого итератора списков.   -  person Michael Dorgan    schedule 25.04.2010
comment
См. Также: stackoverflow.com/questions/396005/   -  person polygenelubricants    schedule 25.04.2010
comment
Я знаю, что вы уже получили довольно обстоятельный ответ, но меня все еще немного смущает этот вопрос. Что, если бы входным списком был {собака, бог, ягненок, блам}?   -  person Dennis Zickefoose    schedule 25.04.2010
comment
Тогда выход должен быть {dog, god}, {lamb, blam} - 2 набора строк, возвращаемых в списке.   -  person Michael Dorgan    schedule 25.04.2010


Ответы (4)


Лучший способ сгруппировать анаграммы - сопоставить строки с каким-либо представлением гистограммы.

 FUNCTION histogram
 [input] -> [output]

 "dog"   -> (1xd, 1xg, 1xo)
 "god"   -> (1xd, 1xg, 1xo)
 "foo"   -> (1xf, 2xo)

По сути, с помощью линейного сканирования строки вы можете создать гистограмму, показывающую, сколько букв в ней содержится. Небольшой конечный алфавит делает это еще проще (например, с A-Z у вас просто есть массив из 26 чисел, по одному на каждую букву).

Теперь анаграммы - это просто слова с одинаковой гистограммой.

Затем у вас может быть структура данных с несколькими картами, которая сопоставляет гистограмму со списком слов, которые имеют эту гистограмму.

MULTIMAP
[key]           => [set of values]

(1xd, 1xg, 1xo) => { "dog", "god" }
(1xf, 2xo)      => { "foo" }

Уловка канонической формы

Вместо работы с гистограммами вы также можете работать с «канонической формой» строк. По сути, вы определяете для каждой строки, какова ее каноническая форма, и два слова являются анаграммами, если они имеют одинаковую каноническую форму.

Одна удобная каноническая форма - расположение букв в строке в отсортированном порядке.

FUNCTION canonize
[input]  -> [output]

"dog"    -> "dgo"
"god"    -> "dgo"
"abracadabra" -> "aaaaabbcdrr"

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

Это наиболее практичное решение вашей проблемы на реальном языке программирования.

Сложность

Создание гистограммы / канонической формы слова составляет практически O(1) (конечный размер алфавита, конечная максимальная длина слова).

При хорошей реализации хеширования get и put на мульти-карте равны O(1).

У вас даже может быть несколько мультиотображений, по одному на каждое слово.

Если есть N слов, то размещение всех слов в мультиотображении будет O(N); тогда вывод каждой группы анаграмм просто сбрасывает значения в мультиотображениях. Это тоже можно сделать в O(N).

Это, безусловно, лучше, чем проверка, является ли каждая пара слов анаграммой (алгоритм O(N^2)).

person polygenelubricants    schedule 25.04.2010
comment
Изначально я начал идти по этому пути - считать буквы и тому подобное, но мне сказали, что это будет слишком медленно. Хм. Разве multimap не является поиском O (nlogn)? Вам нужно будет создать хеш, чтобы приблизить его к o (n) при поиске с другим n при создании, которое все еще является линейным. - person Michael Dorgan; 25.04.2010
comment
@Michael: если вы можете исправить свой код и / или объяснить свой алгоритм псевдокодом, я могу гораздо больше прокомментировать сложность. Если вы вообще не используете какую-либо структуру данных, то ваш алгоритм, вероятно, O(N^2), но я пока не могу это подтвердить. Multimap может иметь O(1) амортизированный доступ. Кроме того, я не уверен, что n имеет в виду ваш комментарий. - person polygenelubricants; 25.04.2010
comment
На моем конце работает нормально - странно. При необходимости могу выложить свой драйвер. Псевдо-код сортирует строки (при условии, что там O (nlogn)), затем просматривает отсортированный список и проверяет идентичные отсортированные строки. Если он существует, удалите его, чтобы в будущем сканирование не дублировало работу. Наихудший случай в этом прогоне - O ^ 2/2, если совпадений не найдено, и O (n) в лучшем случае, если совпадают все строки. - person Michael Dorgan; 25.04.2010
comment
@Michael: Кроме того, multimap<K,V> можно эмулировать с помощью map<K,set<V>>. Я уверен, что C ++ имеет как минимум хорошую map и set реализацию. Если нет, вы можете просто поместить пары [word, canonize(word)] в список и отсортировать по второй части пары. После этого анаграммы появятся рядом друг с другом в отсортированном списке. Всего будет O(N log N). - person polygenelubricants; 25.04.2010
comment
@Michael: Я добавил ссылку на более старый вопрос, в котором есть хорошее решение C # от Джона Скита, которое должно быть легко переведено на C ++. - person polygenelubricants; 25.04.2010
comment
Ух ты, моя паста была такой плохой, что мой typedef покалечился. Я использую пары в списке для своего метода :) - person Michael Dorgan; 25.04.2010

Я просто буду рассматривать это как бесплатную функцию get_anagrams, поскольку class Anagram, похоже, больше ничего не делает.

Выбор list и set ничуть не лучше, чем что-либо еще, так что пока я буду делать это, я сделаю их обоих vector. Фактически, на выходе может быть только одна плоская последовательность. Так что называйте это anagram_sort. Я возьму спецификацию «список» и «набор» в качестве базовых конструкций, а не буквальных шаблонов классов. Также «заданный… возврат» можно в общих чертах интерпретировать как изменение ввода на месте. При желании вызывающий может создать копию.

struct anagram_less { bool operator()( string const &l, string const &r ) {
    string sl( l ), sr( r );
    sort( sl.begin(), sl.end() );
    sort( sr.begin(), sr.end() );
    return sl < sr;
} };

void anagram_sort( vector< string > &v ) { // "the answer"
    sort( v.begin(), v.end(), anagram_less );
} // 10 lines

   // usage:

void print_anagrams( vector< string > &&v ) { // destructive => use rvalue ref
    anagram_sort( v );

    for ( vector< string >::iterator i = v.begin(); i != v.end(); /*++i*/ ) {

        vector< string >::iterator e // find end of run of anagrams
            = adjacent_find( i, v.end(), anagram_less() );
        if ( e != v.end() ) ++ e; // usually need this after adjacent_find :v(

        cout << "( ";
        for ( ; i != e; ++ i ) {
            cout << * i << " ";
        }
        cout << ")" << endl;
    }
}

Это неоптимально, так как слова сортируются повторно. Я предпочитаю сократить количество вопросов на собеседовании, чем делать что-то быстрое «на бумаге».

Чтобы выжать этот последний бит производительности, я бы, вероятно, сделал копию ввода с прикрепленными индексами серийных номеров, отсортировал символы строк, отсортировал строки, а затем использовал индексы для изменения порядка входного вектора с помощью этот алгоритм. Конечное время выполнения с низким коэффициентом O (n log n), как и должно быть.

person Potatoswatter    schedule 25.04.2010

Алгоритм проверки того, являются ли две строки анаграммами друг друга, выглядит следующим образом.

  1. преобразовать две строки таким образом, чтобы они содержали только буквы (потому что свекровь и женщина Гитлер также являются анаграммой). Также сделать регистр двух строк равным означает, что обе строки находятся в верхнем или нижнем регистре.

  2. теперь отсортируйте символы в обеих строках.

  3. сравнить две строки, если они равны, означает, что они являются анаграммами друг друга

Вот код для этого алгоритма

bool checkanagrams(string first,string second)
{

    string tempfirst,tempsecond;    
    int countfirst  = 0;  
    int countsecond = 0;        
   // only storing characters removing other junk things  like -
   for(int i=0;i<first.length();i++)
   {
      if(isalpha(first[i])
      { 
        tempfirst[countfirst] =first[i];
    countfirst++; 
      }

   }
   for(int i=0;i<second.length();i++)
   {
      if(isalpha(second[i])
      { 
        tempsecond[countsecond] =second[i];
    countsecond++; 
      }        

   } 
   sort(tempfirst.begin(),tempfirst.end());  
   sort(tempsecond.begin(),tempsecond.end());
   if(!strcmp(tempfirst.c_str(),tempsecond.c_str())
    return true;
   else
    return false;     

}
person Kapil Vermani    schedule 11.03.2011

Я бы все поместил в хеш-таблицу. Затем для каждой строки я возвращаю ее и проверяю, существует ли она в хеш-таблице, если да, возвращаю как перевернутую строку, так и саму себя в наборе.

Этот подход также позволяет находить палиндромы в наборе.

person Chivalryman    schedule 28.01.2012
comment
Фактически, он находит только палиндромы, но не анаграммы. - person Nobody moving away from SE; 27.10.2012