Лучшее решение для проверки анаграммы?

У меня проблема с перестановкой/анаграммой, и я хотел узнать о наиболее эффективных способах проверки. Теперь я делаю это на языке Java, и поэтому есть библиотека для ВСЕГО, включая сортировку. Первым способом проверки того, являются ли две строки анаграммами друг друга, является проверка длины, их сортировка каким-либо образом, а затем сравнение каждого индекса указанной строки. Код ниже:

private boolean validAnagram(String str, String pair) {
if(str.length() != pair.length()){
    return false;
}

char[] strArr = str.toCharArray();
char[] pairArr = pair.toCharArray();


Arrays.sort(strArr);
str = new String(strArr);

Arrays.sort(pairArr);
pair = new String(pairArr);

for(int i = 0; i<str.length(); i++){
    if(str.charAt(i) != pair.charAt(i)){
        return false;
    }
}
return true;
}

В качестве альтернативы я решил, что было бы проще проверить на основе значения ascii и избежать проверки каждого возможного символа. Код ниже:

private boolean validAnagram(String str, String pair) {
if(str.length() != pair.length()){
    return false;
}

char[] strArr = str.toCharArray();
char[] pairArr = pair.toCharArray();



int strValue = 0;
int pairValue = 0;

for(int i =0; i < strArr.length; i++){
    strValue+= (int) strArr[i];
    pairValue+= (int) pairArr[i];
}

if(strValue != pairValue){
    return false;
}
return true;
}

Итак, какое решение лучше? Я мало что знаю о том, что дает мне Arrays, однако это более распространенный ответ, когда я просматриваю старые интернеты. Заставляет меня задуматься, не упускаю ли я что-то.


person Drew L. Facchiano    schedule 06.07.2016    source источник
comment
Вместо того, чтобы преобразовывать char[] обратно в String, а затем выполнять charAt(), вы можете напрямую сравнивать символы в массивах.   -  person QBrute    schedule 06.07.2016
comment
Это смущает. Вы хотите только анаграммы или любую перестановку? Способы проверки того или другого сильно различаются.   -  person fge    schedule 06.07.2016
comment
Я почти уверен, что второе решение не работает. Он вернет true для «ac» и «bb».   -  person Bastien Aracil    schedule 06.07.2016
comment
Бастьен, вы очень правы :)   -  person Drew L. Facchiano    schedule 06.07.2016


Ответы (11)


Есть несколько способов проверить, являются ли две строки анаграммами или нет. Ваш вопрос в том, какое решение лучше. Ваше первое решение имеет логику сортировки. Сортировка имеет наихудшую сложность (nlogn) . Ваша вторая логика использует только один цикл со сложностью O(n) .

Таким образом, из этих двух ваше второе решение, имеющее сложность только O(n), будет лучшим решением, чем первое.

One possible solution :

private boolean checkAnagram(String stringOne , String stringTwo){
        char[] first = stringOne.toLowerCase().toCharArray(); 
        char[] second = stringTwo.toLowerCase().toCharArray();
        // if length of strings is not same 
        if (first.length != second.length)
            return false;
        int[] counts = new int[26]; 
        for (int i = 0; i < first.length; i++){
            counts[first[i]-97]++;  
            counts[second[i]-97]--;   
        }
        for (int i = 0; i<26; i++)
            if (counts[i] != 0)
                return false;
        return true;
    }

person Pratik Upacharya    schedule 06.07.2016
comment
Эй, Пратик! Это была моя первоначальная мысль. Однако мне было указано, что у моего решения ascii есть серьезная проблема. Можно получить неправильные решения на основе определенной комбинации. На это указал этот молодец на Reddit, если вы дадите ему строки AD и BC. Первый имеет значения ascii 65 и 68, второй имеет значения 66 и 67. Они оба в сумме дают 133 и будут рассматриваться вашим алгоритмом как равные. Однако, похоже, есть обходные пути. Ради проблемы, похоже, не стоит исправлять крайние случаи. - person Drew L. Facchiano; 06.07.2016
comment
Вы можете использовать другой подход, который использует hashmap . - person Pratik Upacharya; 06.07.2016
comment
Я это вижу. Сопоставление каждого символа с логическим значением, а затем сравнение двух карт. Это все еще кажется, что вы получите худшее время выполнения, чем подход сортировки. - person Drew L. Facchiano; 06.07.2016
comment
Я добавил решение, пожалуйста, посмотрите. Подход к сортировке будет иметь (nlogn) сложность в худшем случае, но этот подход будет иметь сложность o(n) в худшем случае. - person Pratik Upacharya; 06.07.2016
comment
Он вылетит, если в строке есть пробел. Вы должны удалить их в первую очередь. Что-то вроде char[] first = stringOne.toLowerCase().replaceAll("\\s+","").toCharArray(); - person ForguesR; 06.07.2016
comment
Это также приведет к сбою, если в строке есть что-то еще, кроме букв (от a до z или от A до Z). - person ForguesR; 06.07.2016

Вот очень простая реализация.

public boolean isAnagram(String strA, String strB) {
  // Cleaning the strings (remove white spaces and convert to lowercase)
  strA = strA.replaceAll("\\s+","").toLowerCase();
  strB = strB.replaceAll("\\s+","").toLowerCase();

  // Check every char of strA and removes first occurence of it in strB
  for (int i = 0; i < strA.length(); i++ ) {
    if (strB.equals("")) return false;  // strB is already empty : not an anagram
    strB = strB.replaceFirst(Pattern.quote("" + strA.charAt(i)), "");
  }

  // if strB is empty we have an anagram
  return strB.equals("");
}

И наконец :

System.out.println(isAnagram("William Shakespeare", "I am a weakish speller")); // true
person ForguesR    schedule 06.07.2016
comment
@ user1090751 Согласно Википедии: анаграмма — это слово или фраза, образованная путем перестановки букв другого слова или фразы, как правило, с использованием всех исходных букв ровно один раз. - person ForguesR; 14.03.2018
comment
Ответ: разве AR не является анаграммой RAT? - person user1090751; 14.03.2018
comment
@ user1090751 Нет, потому что буква T не используется. Искусство — это анаграмма Крысы. - person ForguesR; 14.03.2018
comment
Анаграммы не обязательно должны использовать все буквы...... src: en.wikipedia.org/ вики/анаграмма - person user1090751; 14.03.2018
comment
... но они обычно делают. Все решения здесь относятся к обычным анаграммам. Не стесняйтесь дать свой ответ. - person ForguesR; 14.03.2018
comment
У меня нет решения, и ваше решение не является общим и не может считаться правильным ответом. - person user1090751; 14.03.2018

Это гораздо более простое и легко читаемое решение, которое мне удалось скомпилировать...

    static boolean isAnagram(String a, String b) {
    if (a.length() == b.length()){
        char[] arr1 = a.toLowerCase().toCharArray();
        char[] arr2 = b.toLowerCase().toCharArray();
        Arrays.sort(arr1);
        Arrays.sort(arr2);
        if (Arrays.equals(arr1, arr2)) return true;
        else return false;
    }else return false;
}

Лучший, Джастин

person Justin Gorny    schedule 20.06.2018

Мое решение: временная сложность = O (n)

public static boolean isAnagram(String str1, String str2) {
    if (str1.length() != str2.length()) {
        return false;
    }

    for (int i = 0; i < str1.length(); i++) {
        char ch = str1.charAt(i);

        if (str2.indexOf(ch) == -1) 
            return false;
        else
            str2 = str2.replaceFirst(String.valueOf(ch), " ");
    }

    return true;
}

Прецедент :

@Test
public void testIsPernutationTrue() {
    assertTrue(Anagram.isAnagram("abc", "cba"));
    assertTrue(Anagram.isAnagram("geeksforgeeks", "forgeeksgeeks"));
    assertTrue(Anagram.isAnagram("anagram", "margana"));
}

@Test
public void testIsPernutationFalse() {
    assertFalse(Anagram.isAnagram("abc", "caa"));
    assertFalse(Anagram.isAnagram("anagramm", "marganaa"));
}
person Sameer Shrestha    schedule 20.06.2018
comment
Это O(n^2), потому что str2.indexOf нужно каждый раз перебирать всю строку. - person Thilo; 29.10.2019

Я попробовал несколько решений с использованием наборов и заставил каждое из них запускаться 10 миллионов раз для тестирования с использованием вашего примерного массива:

private static String[] input = {"tea", "ate", "eat", "apple", "java", "vaja", "cut", "utc"};

Во-первых, метод, который я использовал для вызова этих алгоритмов:

public static void main(String[] args) {
    long startTime = System.currentTimeMillis();
    for (int x = 0; x < 10000000; x++) {
        Set<String> confirmedAnagrams = new HashSet<>();
        for (int i = 0; i < (input.length / 2) + 1; i++) {
            if (!confirmedAnagrams.contains(input[i])) {
                for (int j = i + 1; j < input.length; j++) {
                        if (isAnagrams1(input[i], input[j])) {
                            confirmedAnagrams.add(input[i]);
                            confirmedAnagrams.add(input[j]);
                        }
                }
            }
        }
        output = confirmedAnagrams.toArray(new String[confirmedAnagrams.size()]);
    }
    long endTime = System.currentTimeMillis();
    System.out.println("Total time: " + (endTime - startTime));
    System.out.println("Average time: " + ((endTime - startTime) / 10000000D));
}

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

Мои алгоритмы и время их выполнения:

Алгоритм 1:

    private static boolean isAnagrams1(String x, String y) {
    if (x.length() != y.length()) {
        return false;
    } else if (x.equals(y)) {
        return true;
    }

    Set<Character> anagramSet = new HashSet<>();
    for (int i = 0; i < x.length(); i++) {
        anagramSet.add(x.charAt(i));
        anagramSet.add(y.charAt(i));
    }

    return anagramSet.size() != x.length();
}

Это имеет время выполнения:

Total time: 6914
Average time: 6.914E-4

Алгоритм 2

private static boolean isAnagrams2(String x, String y) {
    if (x.length() != y.length()) {
        return false;
    } else if (x.equals(y)) {
        return true;
    }

    Set<Character> anagramSet = new HashSet<>();
    char[] xAr = x.toCharArray();
    char[] yAr = y.toCharArray();
    for (int i = 0; i < xAr.length; i++) {
        anagramSet.add(xAr[i]);
        anagramSet.add(yAr[i]);
    }

    return anagramSet.size() != x.length();
}

Имеет время выполнения:

Total time: 8752
Average time: 8.752E-4

Алгоритм 3

Для этого алгоритма я решил отправить Set, поэтому я создаю его только один раз для каждого цикла и очищаю после каждого теста.

    private static boolean isAnagrams3(Set<Character> anagramSet, String x, String y) {
    if (x.length() != y.length()) {
        return false;
    } else if (x.equals(y)) {
        return true;
    }

    for (int i = 0; i < x.length(); i++) {
        anagramSet.add(x.charAt(i));
        anagramSet.add(y.charAt(i));
    }

    return anagramSet.size() != x.length();
}

Имеет время выполнения:

Total time: 8251
Average time: 8.251E-4

Алгоритм 4

Этот алгоритм не мой, он принадлежит Pratik Upacharya, который тоже ответил на вопрос, чтобы я мог сравнить:

    private static boolean isAnagrams4(String stringOne, String stringTwo) {
    char[] first = stringOne.toLowerCase().toCharArray();
    char[] second = stringTwo.toLowerCase().toCharArray();
    // if length of strings is not same 
    if (first.length != second.length) {
        return false;
    }
    int[] counts = new int[26];
    for (int i = 0; i < first.length; i++) {
        counts[first[i] - 97]++;
        counts[second[i] - 97]--;
    }
    for (int i = 0; i < 26; i++) {
        if (counts[i] != 0) {
            return false;
        }
    }
    return true;
}

Имеет время выполнения:

Total time: 5707
Average time: 5.707E-4

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

* Отредактировано, поскольку я допустил ошибку в своем первоначальном методе, алгоритм Pratik Upacharya's кажется более быстрым.

person Propagandian    schedule 06.07.2016
comment
Ваш Алгоритм 1 возвращает true вместо isAnagrams1("good", "dogg"), вам нужно убедиться, что каждый символ появляется одинаковое количество раз. - person Kyriakos; 06.07.2016
comment
Да, тогда сет действительно не сработает. Прости за это. - person Propagandian; 06.07.2016
comment
Вы можете использовать HashMap<Character, Integer> и увеличивать/уменьшать счетчики аналогично тому, что Pratik делает с массивами. - person Kyriakos; 06.07.2016

Лучшее решение зависит от вашей цели, размера кода, объема памяти или минимальных вычислений.

Очень классное решение, меньше кода, насколько это возможно, не самое быстрое O (nlog n) и довольно неэффективная память в Java 8:

public class Anagram {
  public static void main(String[] argc) {
    String str1 = "gody";
    String str2 = "dogy";

    boolean isAnagram =
    str1.chars().mapToObj(c -> (char) c).sorted().collect(Collectors.toList())
    .equals(str2.chars().mapToObj(c -> (char) c).sorted().collect(Collectors.toList()));

    System.out.println(isAnagram);
  }
}
person ModernBison    schedule 06.07.2016
comment
Это решение имеет некоторые недостатки. В соответствии с вашим решением вы сортируете символы из строк, полученных в параметрах метода, но не игнорируете пробелы и верхний регистр, поэтому, например: isAnagram (Уильям Шекспир, я слабый орфограф), упомянутый выше, возвращает false вместо true. - person K.Rzepecka; 30.04.2017

Рекрутер недавно попросил меня решить эту проблему. Изучая проблему, я нашел решение, которое решает два типа проблем с анаграммами.

проблема 1: определить, существует ли анаграмма в тексте.

проблема 2: определить, существует ли формальная анаграмма в тексте. В этом случае анаграмма должна быть того же размера, что и текст, с которым вы ее сравниваете. В первом случае два текста не обязательно должны быть одинакового размера.
Один просто должен содержать другой.

Мой подход был следующим:

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

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

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

    boolean looking = true;
        for (Anagram ele : smaller.values()) {
            Anagram you = larger.get(ele);
                if (you == null || you.getCount() != ele.getCount()) {
                    looking = false;
                    break;
                }
        }
        return looking;

Обратите внимание, что я создаю ADT для хранения обрабатываемых строк. Сначала они преобразуются в карту.

Вот фрагмент кода для создания объекта анаграммы:

    private void init(String teststring2) {
        StringBuilder sb = new StringBuilder(teststring2);
        for (int i = 0; i &lt sb.length(); i++) {
            Anagram a = new AnagramImpl(sb.charAt(i));
            Anagram tmp = map.putIfAbsent(a, a);
            if (tmp != null) {
                tmp.updateCount();
            }
        }
    }
person George Curington    schedule 09.01.2018

Рассмотрите возможность использования HashMap и Arrays.sort.

    private static Map<String, String> getAnagrams(String[] data) {

    Map<String, String> anagrams = new HashMap<>();
    Map<String, String> results = new HashMap<>();

    for (int i = 0; i < data.length; i++) {

        char[] chars = data[i].toLowerCase().toCharArray();
        Arrays.sort(chars);

        String sorted = String.copyValueOf(chars);

        String item = anagrams.get(sorted);
        if (item != null) {
            anagrams.put(sorted, item + ", " + i);
            results.put(sorted, anagrams.get(sorted));
        } else {
            anagrams.put(sorted, String.valueOf(i));
        }
    }

    return results;
}

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

person mherBaghinyan    schedule 18.06.2019

Я придумал решение, и я даже не использую массив из 26 символов... Проверьте это:

StringBuffer a = new StringBuffer();
        a.append(sc.next().toLowerCase());

        StringBuffer b = new StringBuffer();
        b.append(sc.next().toLowerCase());
        if(a.length() !=b.length())
        {
            System.out.println("NO");
            continue;
        }
        int o =0;
        for(int i =0;i<a.length();i++)
        {
            if(a.indexOf(String.valueOf(b.charAt(i)))<0)
            {
               System.out.println("NO");
               o=1;break; 

            }
        }
        if(o==0)
         System.out.println("Yes");
person Ritveak    schedule 05.06.2019
comment
Это O(n^2), потому что a.indexOf нужно каждый раз перебирать всю строку. - person Thilo; 29.10.2019

Решение с использованием примитивного типа данных.

boolean isAnagram(char input1[], char input2[]) {
    int bitFlip = 32;

    if(input2.length != input1.length){return false;}

    boolean found = false;
    for (int x = 0; x < input1.length; x++) {
        found = false;
        for (int y = 0; y < input2.length; y++) {
             if (!found && ((input1[x] | bitFlip)) ==
             ( (input2[y] | bitFlip))) {
                found = true;
                input2[y] = 0;
            }
        }
        if (!found) {
            break;
        }
    }
    return found ;
}

Этот подход не зависит от какой-либо утилиты сортировки. Что он делает, так это находит значение с помощью итерации, и после того, как он его нашел, он устанавливает его в ноль, чтобы избежать ввода с повторяющимся символом, таким как «пул» и «цикл», который имеет 2 буквы «о».

Он также игнорирует регистры, не полагаясь на toLowerCase(), переворачивая бит, потому что, если 6-й бит (32 в десятичной системе) равен единице, это строчная буква и заглавная, если он равен нулю.

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

Это решение протестировано в hackerrank

person d12ei    schedule 21.02.2020
comment
в качестве ответа, не могли бы вы объяснить, чем ваше решение лучше? - person Crocsx; 21.02.2020

person    schedule
comment
Я бы не назвал алгоритм лучшим, когда он принимает две строки, которые не являются ни анаграммами, ни перестановками друг друга, поскольку строка является анаграммой. - person Tom; 30.12.2017