From 93e417544d8c695c4954ac1434d3596e418acfe8 Mon Sep 17 00:00:00 2001 From: Alex Klymenko Date: Mon, 26 Aug 2024 07:26:01 +0200 Subject: [PATCH] refactor: `Anagrams` (#5390) --- .../com/thealgorithms/strings/Anagrams.java | 233 +++++++++--------- .../thealgorithms/strings/AnagramsTest.java | 55 +++-- 2 files changed, 151 insertions(+), 137 deletions(-) diff --git a/src/main/java/com/thealgorithms/strings/Anagrams.java b/src/main/java/com/thealgorithms/strings/Anagrams.java index f5e8fa84..4b24979e 100644 --- a/src/main/java/com/thealgorithms/strings/Anagrams.java +++ b/src/main/java/com/thealgorithms/strings/Anagrams.java @@ -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 nm = new HashMap<>(); - HashMap 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 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; } } diff --git a/src/test/java/com/thealgorithms/strings/AnagramsTest.java b/src/test/java/com/thealgorithms/strings/AnagramsTest.java index ba530cff..88f6e0bb 100644 --- a/src/test/java/com/thealgorithms/strings/AnagramsTest.java +++ b/src/test/java/com/thealgorithms/strings/AnagramsTest.java @@ -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 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())); } }