2012-05-23 3 views
8

Это вопрос интервью (экран телефона): напишите функцию (на Java), чтобы найти все перестановки данного слова, которые появляются в данном тексте. Например, для слова abc и текста abcxyaxbcayxycab функция должна возвращать abc, bca, cab.Как найти все перестановки данного слова в заданном тексте?

Я бы ответил на этот вопрос следующим образом:

  • Очевидно, что я могу цикл по всем перестановкам данного слова и использовать стандартную substring функции. Однако может быть трудно (для меня прямо сейчас) написать код для генерации всех перестановок слов.

  • Легче перебрать все текстовые подстроки размера слова, отсортировать каждую подстроку и сравнить ее с «отсортированным» заданным словом. Я могу сразу написать такую ​​функцию.

  • Возможно, я могу изменить некоторый алгоритм поиска подстроки, но я не помню эти алгоритмы сейчас.

Как вы бы ответили на этот вопрос?

ответ

11

Это, пожалуй, не самое эффективное решение алгоритмически, но оно чистое с точки зрения дизайна класса. Это решение использует подход сравнения «отсортированных» данных слов.

Можно сказать, что слово является перестановкой другого, если оно содержит одни и те же буквы в одном и том же числе. Это означает, что вы можете преобразовать слово из String в Map<Character,Integer>. Такое преобразование будет иметь сложность O (n), где n - длина String, при условии, что вложения в вашу стоимость реализации Map O (1).

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

Пример. Аббв преобразуется в [a->1, b->2, c->1]

bacb преобразуется в [a->1, b->2, c->1]

Так что, если вы должны знать, если два слова одна перестановка с другой стороны, вы можете конвертировать их обоих в карты, а затем вызвать Map.equals ,

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

Улучшение предложенного Inerdial

Этого подход может быть улучшено путем обновления карты в моде «прокатки».

I.e. если вы соответствуете индексу i=3 в примере стога сена в OP (подстрока xya), то карта будет [a->1, x->1, y->1]. Когда вы продвигаетесь в стоге сена, уменьшите количество символов на haystack[i] и увеличите счетчик на haystack[i+needle.length()].

(Отбрасывание нулей, чтобы убедиться, что Map.equals() работы, или просто реализации пользовательских сравнения.)

Улучшения предложенного Макс

Что делать, если мы также ввести matchedCharactersCnt переменные? В начале стога сена будет 0. Каждый раз, когда вы меняете карту на нужное значение, вы увеличиваете переменную. Каждый раз, когда вы меняете его на желаемое значение, вы уменьшаете переменную. На каждой итерации вы проверяете, равна ли переменная длине иглы. Если это так - вы нашли совпадение. Это будет быстрее, чем сравнение полной карты каждый раз.

псевдокод обеспечивает Макс:

needle = "abbc" 
text = "abbcbbabbcaabbca" 

needleSize = needle.length() 
//Map of needle character counts 
targetMap = [a->1, b->2, c->1] 

matchedLength = 0 
curMap = [a->0, b->0, c->0] 
//Initial map initialization 
for (int i=0;i<needle.length();i++) { 
    if (curMap.contains(haystack[i])) { 
     matchedLength++ 
     curMap[haystack[i]]++ 
    } 
} 

if (matchedLength == needleSize) { 
    System.out.println("Match found at: 0"); 
} 

//Search itself 
for (int i=0;i<haystack.length()-needle.length();i++) { 
    int targetValue1 = targetMap[haystack[i]]; //Reading from hashmap, O(1) 
    int curValue1 = curMap[haystack[i]]; //Another read 
    //If we are removing beneficial character 
    if (targetValue1 > 0 && curValue1 > 0 && curValue1 <= targetValue1) {  
     matchedLength--; 
    } 
    curMap[haystack[i]] = curValue1 + 1; //Write to hashmap, O(1) 


    int targetValue2 = targetMap[haystack[i+needle.length()]] //Read 
    int curValue2 = curMap[haystack[i+needle.length()]] //Read 
    //We are adding a beneficial character 
    if (targetValue2 > 0 && curValue2 < targetValue2) { //If we don't need this letter at all, the amount of matched letters decreases 
     matchedLength++; 
    } 
    curMap[haystack[i+needle.length()]] = curValue2 + 1; //Write 

    if (matchedLength == needleSize) { 
     System.out.println("Match found at: "+(i+1)); 
    } 
} 

//Basically with 4 reads and 2 writes which are 
//independent of the size of the needle, 
//we get to the maximal possible performance: O(n) 
+0

этого ответ кажется неполным. Вы говорите, как вы собираетесь канонизировать слово, но не говорите ничего о поиске перестановок в тексте. Вы бы использовали ту же идею, что и плакаты? –

+1

В сочетании с второй идеей OP этот подход можно улучшить, обновив карту в режиме «прокатки». То есть если вы соответствуете индексу 'i = 3' в примере haystack в OP (подстрока' xya'), карта будет '[a-> 1, x-> 1, y-> 1]'. Когда вы продвигаетесь в стоге сена, уменьшите количество символов для 'haystack [i]' и увеличьте количество для 'haystack [i + needle.length()]'. (Отбрасывание нулей, чтобы убедиться, что 'Map.equals()' работает или просто реализует пользовательское сравнение.) – millimoose

+0

@Inerdial ваше улучшение действительно элегантно! Congrats !! –

3

Вы должны быть в состоянии сделать это за один проход. Начните с создания карты, содержащей все символы слова, которое вы ищете. Поэтому изначально карта содержит [a, b, c].

Теперь пройдите текст по одному символу за раз. Цикл выглядит примерно так, в псевдокоде.

found_string = ""; 
for each character in text 
    if character is in map 
     remove character from map 
     append character to found_string 
     if map is empty 
      output found_string 
      found_string = "" 
      add all characters back to map 
     end if 
    else 
     // not a permutation of the string you're searching for 
     refresh map with characters from found_string 
     found_string = "" 
    end if 
end for 

Если вы хотите уникальные вхождения, измените шаг вывода так, чтобы он добавил найденные строки к карте. Это устранит дубликаты.

Существует проблема слов, содержащих дублированные буквы. Если это проблема, сделайте ключ буквой, а значение - счетчиком. «Удаление» символа означает уменьшение его количества на карте. Если счетчик переходит в 0, тогда символ фактически удаляется с карты.

Алгоритм, как написано, не найдет перекрывающиеся вхождения. То есть, учитывая текст abcba, он найдет только abc. Если вы хотите обрабатывать перекрывающиеся вхождения, вы можете изменить алгоритм так, чтобы, когда он находит совпадение, он уменьшает индекс на единицу за вычетом длины найденной строки.

Это была забавная головоломка. Благодарю.

+0

Согласитесь: забавная головоломка.Я просто отмечаю, что на код в моем ответе влияет проблема с дублирующимися письмами. Я отредактирую код, вдохновленный вашим ответом. –

1

Это то, что я хотел бы сделать - создать массив флага с одним элемента, равных 0 или 1, чтобы указать, является ли этот символ в НТР был согласован

Установите первую строку результата РЕЗУЛЬТАТА опорожнить.

для каждого символа C в тексте:

указан массив Х равен длине СТО на все нули.

для каждого символа S в STR: Если С является характер JTH в СТО и Х [J] == 0, то положим Х [Дж] < = 1 и добавить С к результату. Если длина RESULT равна STR, добавьте RESULT в список перестановок и снова установите элементы X [] в ноль.

Если C не является символом J в STR, у которого X [J] == 0, , то снова установите элементы X [] в ноль.

1

Второй подход кажется очень изящным для меня и должен быть совершенно приемлемым. Я думаю, что он весит O(M * N log N), где N - длина слова и M - длина текста.

Я могу придумать несколько более сложный O(M) алгоритм:

  1. графа появление каждого символа в слове
  2. Сделайте то же самое для первого N (т.е. length(word)) символов текста
  3. Вычитание двух частотных векторов, получая subFreq
  4. Подсчитайте число не-нулей в subFreq, получая numDiff
  5. Если numDiff равен нуль, есть совпадение
  6. Update subFreq и numDiff в постоянная время пути обновления для первого и после последнего символа в тексте
  7. Перейти к 5 до достижения конца текста

EDIT: Посмотрите, что было опубликовано несколько похожих ответов. Большая часть этого алгоритма эквивалентна подсчету частоты вращения, предложенной другими. Мое скромное дополнение также обновляет количество различий в прокатной моде, давая алгоритм O(M+N), а не O(M*N).

EDIT2: Только что увидел, что Макс в основном предложил это в комментариях, поэтому он указывает на него.

+0

O (M + N) - это огромное улучшение по сравнению с O (M * N) :) – Chip

+0

Я не уверен, почему ваш алгоритм «O (M + N)», вы считаете, что чтение и запись в Map - это ' O (N) '? Я считаю, что ваш алгоритм «O (M)». Для правильной реализации карты (в соответствии с этой задачей) чтение/запись будет «O (1)». – bezmax

+0

@Max его на самом деле O (M), так как N Chip

1

Этот код должен делать свою работу:

import java.util.ArrayList; 
import java.util.List; 

public class Permutations { 
    public static void main(String[] args) { 
     final String word = "abc"; 
     final String text = "abcxaaabbbccyaxbcayxycab"; 
     List<Character> charsActuallyFound = new ArrayList<Character>(); 
     StringBuilder match = new StringBuilder(3); 

     for (Character c : text.toCharArray()) { 
      if (word.contains(c.toString()) && !charsActuallyFound.contains(c)) { 
       charsActuallyFound.add(c); 
       match.append(c); 
       if (match.length()==word.length()) 
       { 
        System.out.println(match); 
        match = new StringBuilder(3); 
        charsActuallyFound.clear(); 
       } 
      } else { 
       match = new StringBuilder(3); 
       charsActuallyFound.clear(); 
      } 
     } 
    } 
} 

The charsActuallyFound Список используется для отслеживания характера уже нашли в петле. Необходимо избегать математического «aaa» «bbb» «ccc» (добавленный мной к указанному вами тексту).

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

abc 
bca 
cab 

но если вы seaarch для слова «ааа», то ничего не печатается, потому что каждый символ не может быть согласован более чем один раз. Вдохновленный от Jim Mischel ответа, я изменить мой код, заканчивая это:

import java.util.ArrayList; 
import java.util.List; 

public class Permutations { 
    public static void main(String[] args) { 
     final String text = "abcxaaabbbccyaxbcayaaaxycab"; 

     printMatches("aaa", text); 
     printMatches("abc", text); 
    } 

    private static void printMatches(String word, String text) { 
     System.out.println("matches for "+word +" in "+text+":"); 

     StringBuilder match = new StringBuilder(3); 
     StringBuilder notYetFounds=new StringBuilder(word); 

     for (Character c : text.toCharArray()) { 
      int idx = notYetFounds.indexOf(c.toString()); 
      if (idx!=-1) { 
       notYetFounds.replace(idx,idx+1,""); 

       match.append(c); 
       if (match.length()==word.length()) 
       { 
        System.out.println(match); 
        match = new StringBuilder(3); 
        notYetFounds=new StringBuilder(word); 
       } 
      } else { 
       match = new StringBuilder(3); 
       notYetFounds=new StringBuilder(word); 
      } 
     } 
     System.out.println(); 
    } 

} 

Это дает мне следующий вывод:

matches for aaa in abcxaaabbbccyaxbcayaaaxycab: 
aaa 
aaa 

matches for abc in abcxaaabbbccyaxbcayaaaxycab: 
abc 
bca 
cab 

ли какому-то тест, приведенный выше код найден 30815 матчей «ABC» в случайная строка 36M всего за 4,5 секунды. Как сказал Джим, спасибо за эту головоломку ...

+0

1. Карта даст МНОГО больше производительности. Эта часть в вашем коде 'notYetFounds.indexOf (c.toString());' делает весь алгоритм «сложным», поскольку сложность «indexOf» в вашей сложности «o (needle.length() * haystack.length())» case - это в основном 'O (needle.length())'. Однако чтение/запись на карту является сложностью «O (1)». Следовательно, алгоритмы, которые используют Map, приводят к сложности 'O (haystack.length()), которая является величиной быстрее (особенно для огромных игл). – bezmax

+0

2. Ваш алгоритм не может обрабатывать перекрывающиеся иглы, и я не вижу способа изменить его, чтобы он обрабатывал их. Например: 'haystack =" abcba "и' needle = "abc" 'приведут к двум совпадениям:' [abc] ba' и 'ab [cba]', в то время как ваш алгоритм производит только первое совпадение (и затем сбрасывает буферы). – bezmax

+0

Да, я видел. если увеличение длины иглы начнется медленно, код начнет медленно работать. Я попытаюсь закодировать другую версию с картами. О, и нет, перекрывающиеся слова не соответствуют моему коду ... –

5

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

Существует метод, в котором вы можете вычислить хэш строки с использованием простых чисел. Каждая перестановка одной и той же строки даст одно и то же значение хэш-функции. Вся другая комбинация строк, которая не является перестановкой, даст некоторое другое значение хэш-функции.

хэш-значение вычисляется с * р + с * р + ... + с п * р п где с я является уникальным значением для текущего символа в строке и где p i - уникальное значение простого числа для c i char.

Вот реализация.

public class Main { 
    static int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 
     19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 
     73, 79, 83, 89, 97, 101, 103 }; 

    public static void main(String[] args) {   
     final char[] text = "abcxaaabbbccyaxbcayaaaxycab" 
      .toCharArray();  
     char[] abc = new char[]{'a','b','c'};  
     int match = val(abc);     
     for (int i = 0; i < text.length - 2; i++) { 
      char[] _123 = new char[]{text[i],text[i+1],text[i+2]};   
      if(val(_123)==match){ 
       System.out.println(new String(_123));  
      } 
     } 
    } 
    static int p(char c) { 
     return primes[(int)c - (int)'a']; 
    } 
    static int val(char[] cs) { 
     return 
     p(cs[0])*(int)cs[0] + p(cs[1])*(int)cs[1] + p(cs[2])*(int)cs[2];   
    } 
} 

Выход этого: а BCA кабины

Смежные вопросы