Überprüfen Sie, ob zwei Zeichenfolgen in Java Anagramme sind

1. Übersicht

Laut Wikipedia ist ein Anagramm ein Wort oder eine Phrase, die durch Umordnen der Buchstaben eines anderen Wortes oder einer anderen Phrase gebildet wird.

Wir können dies bei der Verarbeitung von Zeichenfolgen verallgemeinern, indem wir sagen, dass ein Anagramm einer Zeichenfolge eine andere Zeichenfolge ist, die genau die gleiche Anzahl jedes Zeichens in beliebiger Reihenfolge enthält .

In diesem Tutorial werden wir uns mit der Erkennung ganzer Zeichenfolgenanagramme befassen, bei denen die Anzahl der einzelnen Zeichen gleich sein muss, einschließlich Nicht-Alpha-Zeichen wie Leerzeichen und Ziffern. Zum Beispiel "! Salzarm!" und "Eulen-Lat !!" würde als Anagramme betrachtet werden, da sie genau die gleichen Zeichen enthalten.

2. Lösung

Vergleichen wir einige Lösungen, die entscheiden können, ob zwei Zeichenfolgen Anagramme sind. Jede Lösung prüft zu Beginn, ob die beiden Zeichenfolgen die gleiche Anzahl von Zeichen haben. Dies ist ein schneller Weg, um vorzeitig zu beenden, da Eingaben mit unterschiedlichen Längen keine Anagramme sein können .

Betrachten wir für jede mögliche Lösung die Komplexität der Implementierung für uns als Entwickler. Wir werden auch die zeitliche Komplexität für die CPU unter Verwendung der großen O-Notation untersuchen.

3. Überprüfen Sie durch Sortieren

Wir können die Zeichen jeder Zeichenfolge neu anordnen, indem wir ihre Zeichen sortieren, wodurch zwei normalisierte Zeichenfelder erzeugt werden.

Wenn zwei Zeichenfolgen Anagramme sind, sollten ihre normalisierten Formen gleich sein.

In Java können wir zuerst die beiden Zeichenfolgen in char [] -Arrays konvertieren . Dann können wir diese beiden Arrays sortieren und auf Gleichheit prüfen:

boolean isAnagramSort(String string1, String string2) { if (string1.length() != string2.length()) { return false; } char[] a1 = string1.toCharArray(); char[] a2 = string2.toCharArray(); Arrays.sort(a1); Arrays.sort(a2); return Arrays.equals(a1, a2); } 

Diese Lösung ist leicht zu verstehen und zu implementieren. Die Gesamtlaufzeit dieses Algorithmus beträgt jedoch O (n log n), da das Sortieren eines Arrays von n Zeichen O (n log n) Zeit benötigt.

Damit der Algorithmus funktioniert, muss er eine Kopie beider Eingabezeichenfolgen als Zeichenarrays erstellen und dabei etwas zusätzlichen Speicher verwenden.

4. Überprüfen Sie durch Zählen

Eine alternative Strategie besteht darin, die Anzahl der Vorkommen jedes Zeichens in unseren Eingaben zu zählen. Wenn diese Histogramme zwischen den Eingaben gleich sind, sind die Zeichenfolgen Anagramme.

Um ein wenig Speicherplatz zu sparen, erstellen wir nur ein Histogramm. Wir erhöhen die Anzahl für jedes Zeichen in der ersten Zeichenfolge und verringern die Anzahl für jedes Zeichen in der zweiten. Wenn die beiden Zeichenfolgen Anagramme sind, wird das Ergebnis sein, dass alles auf 0 ausgeglichen wird.

Das Histogramm benötigt eine Zählertabelle mit fester Größe, deren Größe durch die Zeichensatzgröße definiert wird. Wenn wir beispielsweise nur ein Byte zum Speichern jedes Zeichens verwenden, können wir eine Zählarraygröße von 256 verwenden, um das Auftreten jedes Zeichens zu zählen:

private static int CHARACTER_RANGE= 256; public boolean isAnagramCounting(String string1, String string2) { if (string1.length() != string2.length()) { return false; } int count[] = new int[CHARACTER_RANGE]; for (int i = 0; i < string1.length(); i++) { count[string1.charAt(i)]++; count[string2.charAt(i)]--; } for (int i = 0; i < CHARACTER_RANGE; i++) { if (count[i] != 0) { return false; } } return true; }

Diese Lösung ist mit der zeitlichen Komplexität von O (n) schneller . Es benötigt jedoch zusätzlichen Platz für das Zählarray. Bei 256 Ganzzahlen ist das für ASCII nicht schlecht.

Wenn wir jedoch CHARACTER_RANGE erhöhen müssen, um Mehrbyte -Zeichensätze wie UTF-8 zu unterstützen, wird dies sehr speicherhungrig. Daher ist es nur dann wirklich praktisch, wenn die Anzahl der möglichen Zeichen in einem kleinen Bereich liegt.

Aus entwicklungspolitischer Sicht enthält diese Lösung mehr zu wartenden Code und nutzt Java-Bibliotheksfunktionen weniger.

5. Überprüfen Sie mit MultiSet

Mit MultiSet können wir den Zähl- und Vergleichsprozess vereinfachen . MultiSet ist eine Sammlung, die die auftragsunabhängige Gleichheit mit doppelten Elementen unterstützt. Zum Beispiel sind die Multisets {a, a, b} und {a, b, a} gleich.

Um Multiset verwenden zu können , müssen wir zuerst die Guava-Abhängigkeit zu unserer Projektdatei pom.xml hinzufügen :

 com.google.guava guava 28.1-jre  

Wir werden jede unserer Eingabezeichenfolgen in ein MultiSet von Zeichen konvertieren . Dann werden wir prüfen, ob sie gleich sind:

boolean isAnagramMultiset(String string1, String string2) { if (string1.length() != string2.length()) { return false; } Multiset multiset1 = HashMultiset.create(); Multiset multiset2 = HashMultiset.create(); for (int i = 0; i < string1.length(); i++) { multiset1.add(string1.charAt(i)); multiset2.add(string2.charAt(i)); } return multiset1.equals(multiset2); } 

Dieser Algorithmus löst das Problem in O (n) Zeit, ohne ein großes Zählarray deklarieren zu müssen.

Es ähnelt der vorherigen Zähllösung. Anstatt jedoch eine Tabelle mit fester Größe zum Zählen zu verwenden, nutzen wir die MutlitSet- Klasse, um eine Tabelle mit variabler Größe mit einer Anzahl für jedes Zeichen zu simulieren.

Der Code für diese Lösung nutzt mehr Bibliotheksfunktionen auf hoher Ebene als unsere Zähllösung.

6. Briefbasiertes Anagramm

Die bisherigen Beispiele halten sich nicht strikt an die sprachliche Definition eines Anagramms. Dies liegt daran, dass sie Satzzeichen als Teil des Anagramms betrachten und zwischen Groß- und Kleinschreibung unterscheiden.

Passen wir die Algorithmen an, um ein buchstabenbasiertes Anagramm zu ermöglichen. Betrachten wir nur die Neuanordnung von Buchstaben ohne Berücksichtigung der Groß- und Kleinschreibung, unabhängig von anderen Zeichen wie Leerzeichen und Interpunktionen. Zum Beispiel "Ein Dezimalpunkt" und "Ich bin ein Punkt an Ort und Stelle." wären Anagramme voneinander.

Um dieses Problem zu lösen, können wir zuerst die beiden Eingabezeichenfolgen vorverarbeiten, um unerwünschte Zeichen herauszufiltern und Buchstaben in Kleinbuchstaben umzuwandeln. Dann können wir eine der oben genannten Lösungen (z. B. die MultiSet- Lösung) verwenden, um Anagramme für die verarbeiteten Zeichenfolgen zu überprüfen:

String preprocess(String source) { return source.replaceAll("[^a-zA-Z]", "").toLowerCase(); } boolean isLetterBasedAnagramMultiset(String string1, String string2) { return isAnagramMultiset(preprocess(string1), preprocess(string2)); }

Dieser Ansatz kann ein allgemeiner Weg sein, um alle Varianten der Anagrammprobleme zu lösen. Wenn wir beispielsweise auch Ziffern einschließen möchten, müssen wir nur den Vorverarbeitungsfilter anpassen.

7. Fazit

In diesem Artikel haben wir drei Algorithmen untersucht, um zu überprüfen, ob eine bestimmte Zeichenfolge ein Anagramm eines anderen Zeichens für Zeichen ist. Für jede Lösung haben wir die Kompromisse zwischen Geschwindigkeit, Lesbarkeit und erforderlicher Speichergröße erörtert.

Wir haben uns auch angesehen, wie die Algorithmen angepasst werden können, um nach Anagrammen im traditionelleren sprachlichen Sinne zu suchen. Dies haben wir erreicht, indem wir die Eingaben in Kleinbuchstaben vorverarbeitet haben.

Wie immer ist der Quellcode für den Artikel auf GitHub verfügbar.