refactor: Anagrams (#5390)

This commit is contained in:
Alex Klymenko 2024-08-26 07:26:01 +02:00 committed by GitHub
parent 7e9cdad3ee
commit 93e417544d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 151 additions and 137 deletions

View File

@ -10,141 +10,130 @@ import java.util.HashMap;
* also the word binary into brainy and the word adobe into abode.
* Reference from https://en.wikipedia.org/wiki/Anagram
*/
public class Anagrams {
public final class Anagrams {
private Anagrams() {
}
/**
* 4 approaches are provided for anagram checking. approach 2 and approach 3 are similar but
* differ in running time.
* OUTPUT :
* first string ="deal" second string ="lead"
* Output: Anagram
* Input and output is constant for all four approaches
* 1st approach Time Complexity : O(n logn)
* Auxiliary Space Complexity : O(1)
* 2nd approach Time Complexity : O(n)
* Auxiliary Space Complexity : O(1)
* 3rd approach Time Complexity : O(n)
* Auxiliary Space Complexity : O(1)
* 4th approach Time Complexity : O(n)
* Auxiliary Space Complexity : O(n)
* 5th approach Time Complexity: O(n)
* Auxiliary Space Complexity: O(1)
* Checks if two strings are anagrams by sorting the characters and comparing them.
* Time Complexity: O(n log n)
* Space Complexity: O(n)
*
* @param s the first string
* @param t the second string
* @return true if the strings are anagrams, false otherwise
*/
public static void main(String[] args) {
String first = "deal";
String second = "lead";
// All the below methods takes input but doesn't return any output to the main method.
Anagrams nm = new Anagrams();
System.out.println(nm.approach2(first, second)); /* To activate methods for different approaches*/
System.out.println(nm.approach1(first, second)); /* To activate methods for different approaches*/
System.out.println(nm.approach3(first, second)); /* To activate methods for different approaches*/
System.out.println(nm.approach4(first, second)); /* To activate methods for different approaches*/
}
boolean approach1(String s, String t) {
if (s.length() != t.length()) {
return false;
} else {
char[] c = s.toCharArray();
char[] d = t.toCharArray();
Arrays.sort(c);
Arrays.sort(d); /* In this approach the strings are stored in the character arrays and
both the arrays are sorted. After that both the arrays are compared
for checking anangram */
return Arrays.equals(c, d);
}
}
boolean approach2(String a, String b) {
if (a.length() != b.length()) {
return false;
} else {
int[] m = new int[26];
int[] n = new int[26];
for (char c : a.toCharArray()) {
m[c - 'a']++;
}
// In this approach the frequency of both the strings are stored and after that the
// frequencies are iterated from 0 to 26(from 'a' to 'z' ). If the frequencies match
// then anagram message is displayed in the form of boolean format Running time and
// space complexity of this algo is less as compared to others
for (char c : b.toCharArray()) {
n[c - 'a']++;
}
for (int i = 0; i < 26; i++) {
if (m[i] != n[i]) {
return false;
}
}
return true;
}
}
boolean approach3(String s, String t) {
public static boolean approach1(String s, String t) {
if (s.length() != t.length()) {
return false;
}
// this is similar to approach number 2 but here the string is not converted to character
// array
else {
int[] a = new int[26];
int[] b = new int[26];
int k = s.length();
for (int i = 0; i < k; i++) {
a[s.charAt(i) - 'a']++;
b[t.charAt(i) - 'a']++;
}
for (int i = 0; i < 26; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
char[] c = s.toCharArray();
char[] d = t.toCharArray();
Arrays.sort(c);
Arrays.sort(d);
return Arrays.equals(c, d);
}
boolean approach4(String s, String t) {
/**
* Checks if two strings are anagrams by counting the frequency of each character.
* Time Complexity: O(n)
* Space Complexity: O(1)
*
* @param s the first string
* @param t the second string
* @return true if the strings are anagrams, false otherwise
*/
public static boolean approach2(String s, String t) {
if (s.length() != t.length()) {
return false;
}
// This approach is done using hashmap where frequencies are stored and checked iteratively
// and if all the frequencies of first string match with the second string then anagram
// message is displayed in boolean format
else {
HashMap<Character, Integer> nm = new HashMap<>();
HashMap<Character, Integer> kk = new HashMap<>();
for (char c : s.toCharArray()) {
nm.put(c, nm.getOrDefault(c, 0) + 1);
}
for (char c : t.toCharArray()) {
kk.put(c, kk.getOrDefault(c, 0) + 1);
}
// It checks for equal frequencies by comparing key-value pairs of two hashmaps
return nm.equals(kk);
}
}
boolean approach5(String s, String t) {
if (s.length() != t.length()) {
return false;
}
// Approach is different from above 4 aproaches.
// Here we initialize an array of size 26 where each element corresponds to the frequency of
// a character.
int[] freq = new int[26];
// iterate through both strings, incrementing the frequency of each character in the first
// string and decrementing the frequency of each character in the second string.
int[] charCount = new int[26];
for (int i = 0; i < s.length(); i++) {
int pos1 = s.charAt(i) - 'a';
int pos2 = s.charAt(i) - 'a';
freq[pos1]++;
freq[pos2]--;
charCount[s.charAt(i) - 'a']++;
charCount[t.charAt(i) - 'a']--;
}
// iterate through the frequency array and check if all the elements are zero, if so return
// true else false
for (int i = 0; i < 26; i++) {
if (freq[i] != 0) {
for (int count : charCount) {
if (count != 0) {
return false;
}
}
return true;
}
/**
* Checks if two strings are anagrams by counting the frequency of each character
* using a single array.
* Time Complexity: O(n)
* Space Complexity: O(1)
*
* @param s the first string
* @param t the second string
* @return true if the strings are anagrams, false otherwise
*/
public static boolean approach3(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] charCount = new int[26];
for (int i = 0; i < s.length(); i++) {
charCount[s.charAt(i) - 'a']++;
charCount[t.charAt(i) - 'a']--;
}
for (int count : charCount) {
if (count != 0) {
return false;
}
}
return true;
}
/**
* Checks if two strings are anagrams using a HashMap to store character frequencies.
* Time Complexity: O(n)
* Space Complexity: O(n)
*
* @param s the first string
* @param t the second string
* @return true if the strings are anagrams, false otherwise
*/
public static boolean approach4(String s, String t) {
if (s.length() != t.length()) {
return false;
}
HashMap<Character, Integer> charCountMap = new HashMap<>();
for (char c : s.toCharArray()) {
charCountMap.put(c, charCountMap.getOrDefault(c, 0) + 1);
}
for (char c : t.toCharArray()) {
if (!charCountMap.containsKey(c) || charCountMap.get(c) == 0) {
return false;
}
charCountMap.put(c, charCountMap.get(c) - 1);
}
return charCountMap.values().stream().allMatch(count -> count == 0);
}
/**
* Checks if two strings are anagrams using an array to track character frequencies.
* This approach optimizes space complexity by using only one array.
* Time Complexity: O(n)
* Space Complexity: O(1)
*
* @param s the first string
* @param t the second string
* @return true if the strings are anagrams, false otherwise
*/
public static boolean approach5(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] freq = new int[26];
for (int i = 0; i < s.length(); i++) {
freq[s.charAt(i) - 'a']++;
freq[t.charAt(i) - 'a']--;
}
for (int count : freq) {
if (count != 0) {
return false;
}
}

View File

@ -1,23 +1,48 @@
package com.thealgorithms.strings;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
public class AnagramsTest {
@Test
public void isAlphabetical() {
String input1 = "late";
Anagrams anagrams = new Anagrams();
assertTrue(anagrams.approach1(input1, "tale"));
assertTrue(anagrams.approach1(input1, "teal"));
assertTrue(anagrams.approach2(input1, "tale"));
assertTrue(anagrams.approach2(input1, "teal"));
assertTrue(anagrams.approach3(input1, "tale"));
assertTrue(anagrams.approach3(input1, "teal"));
assertTrue(anagrams.approach4(input1, "tale"));
assertTrue(anagrams.approach4(input1, "teal"));
assertTrue(anagrams.approach5(input1, "teal"));
record AnagramTestCase(String input1, String input2, boolean expected) {
}
private static Stream<AnagramTestCase> anagramTestData() {
return Stream.of(new AnagramTestCase("late", "tale", true), new AnagramTestCase("late", "teal", true), new AnagramTestCase("listen", "silent", true), new AnagramTestCase("hello", "olelh", true), new AnagramTestCase("hello", "world", false), new AnagramTestCase("deal", "lead", true),
new AnagramTestCase("binary", "brainy", true), new AnagramTestCase("adobe", "abode", true), new AnagramTestCase("cat", "act", true), new AnagramTestCase("cat", "cut", false));
}
@ParameterizedTest
@MethodSource("anagramTestData")
void testApproach1(AnagramTestCase testCase) {
assertEquals(testCase.expected(), Anagrams.approach1(testCase.input1(), testCase.input2()));
}
@ParameterizedTest
@MethodSource("anagramTestData")
void testApproach2(AnagramTestCase testCase) {
assertEquals(testCase.expected(), Anagrams.approach2(testCase.input1(), testCase.input2()));
}
@ParameterizedTest
@MethodSource("anagramTestData")
void testApproach3(AnagramTestCase testCase) {
assertEquals(testCase.expected(), Anagrams.approach3(testCase.input1(), testCase.input2()));
}
@ParameterizedTest
@MethodSource("anagramTestData")
void testApproach4(AnagramTestCase testCase) {
assertEquals(testCase.expected(), Anagrams.approach4(testCase.input1(), testCase.input2()));
}
@ParameterizedTest
@MethodSource("anagramTestData")
void testApproach5(AnagramTestCase testCase) {
assertEquals(testCase.expected(), Anagrams.approach5(testCase.input1(), testCase.input2()));
}
}