diff --git a/algorithm-exercises-java/src/main/java/ae/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/SherlockAndAnagrams.java b/algorithm-exercises-java/src/main/java/ae/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/SherlockAndAnagrams.java new file mode 100644 index 0000000..b62e7e3 --- /dev/null +++ b/algorithm-exercises-java/src/main/java/ae/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/SherlockAndAnagrams.java @@ -0,0 +1,80 @@ +package ae.hackerrank.interview_preparation_kit.dictionaries_and_hashmaps; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * SherlockAndAnagrams. + * + * @link Problem definition [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.md]] + * @link Solution notes [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams-solution-notes.md]] + */ +public class SherlockAndAnagrams { + private SherlockAndAnagrams() {} + + /** + * factorial(). + */ + public static BigInteger factorial(int number) { + BigInteger result = BigInteger.ONE; + for (int i = 1; i <= number; i++) { + result = result.multiply(new BigInteger(Integer.toString(i))); + } + + return result; + } + + /** + * sherlockAndAnagrams. + */ + public static int sherlockAndAnagrams(String sWord) { + + Map> candidates = new HashMap<>(); + int size = sWord.length(); + + for (int i = 0; i < size; i++) { + for (int j = 0; j < size - i; j++) { + String substr = sWord.substring(i, size - j); + + // Add substrings to a candidate list. + // two strings are anagrams if sorted strings are the same. + String anagramCandidate = Arrays.stream(substr.split("")) + .sorted() + .collect(Collectors.joining()); + + + // Append candidates to dictionary by "sorted string" key + if (candidates.containsKey(anagramCandidate)) { + candidates.get(anagramCandidate).add(substr); + } else { + ArrayList anagrams = new ArrayList<>(); + anagrams.add(substr); + candidates.put(anagramCandidate, anagrams); + } + } + } + + int total = 0; + + // Final Anagram list + for (Map.Entry> entry : candidates.entrySet()) { + int quantityOfAnagrams = entry.getValue().size(); + int k = 2; + + if (quantityOfAnagrams > 1) { + // Binomial coefficient: https://en.wikipedia.org/wiki/Binomial_coefficient + int count = factorial(quantityOfAnagrams).divide( + (factorial(k).multiply(factorial(quantityOfAnagrams - k))) + ).intValue(); + total += count; + } + } + + return total; + } +} diff --git a/algorithm-exercises-java/src/test/java/ae/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/SherlockAndAnagramsTest.java b/algorithm-exercises-java/src/test/java/ae/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/SherlockAndAnagramsTest.java new file mode 100644 index 0000000..5861734 --- /dev/null +++ b/algorithm-exercises-java/src/test/java/ae/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/SherlockAndAnagramsTest.java @@ -0,0 +1,76 @@ +package ae.hackerrank.interview_preparation_kit.dictionaries_and_hashmaps; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import util.JsonLoader; + +/** + * SherlockAndAnagrams. + * + * @link Problem definition [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/two-strings.md]] + */ +@TestInstance(Lifecycle.PER_CLASS) +class SherlockAndAnagramsTest { + + /** + * SherlockAndAnagramsTestCase. + */ + public static class SherlockAndAnagramsTestCase { + /** + * SherlockAndAnagramsTestCase.TestCase. + */ + public static class TestCase { + public String input; + public Integer expected; + } + + public String title; + public List tests; + } + + List testCases; + + /** + * Sets up the test environment by loading test cases from a JSON file. + * The JSON file is located in the specified path relative to the project structure. + * + * @throws IOException if an error occurs while reading the JSON file. + */ + @BeforeAll + void setup() throws IOException { + String path = String.join("/", "hackerrank", + "interview_preparation_kit", + "dictionaries_and_hashmaps", + "sherlock_and_anagrams.testcases.json"); + + this.testCases = JsonLoader.loadJson(path, SherlockAndAnagramsTestCase.class); + } + + private SherlockAndAnagramsTest() {} + + /** + * sherlockAndAnagrams. + */ + @Test void sherlockAndAnagrams() { + for (SherlockAndAnagramsTestCase _testCases : this.testCases) { + + for (SherlockAndAnagramsTestCase.TestCase test : _testCases.tests) { + Integer solutionFound = SherlockAndAnagrams.sherlockAndAnagrams(test.input); + + assertEquals(test.expected, solutionFound, + "%s(%s) answer must be: %s".formatted( + "SherlockAndAnagrams.sherlockAndAnagrams", + test.input, + test.expected + ) + ); + } + } + } +} diff --git a/algorithm-exercises-java/src/test/resources/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.testcases.json b/algorithm-exercises-java/src/test/resources/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.testcases.json new file mode 100644 index 0000000..f0e7c3f --- /dev/null +++ b/algorithm-exercises-java/src/test/resources/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.testcases.json @@ -0,0 +1,92 @@ +[ + { + "title": "Sample Test Case 0", + "tests": [ + { + "input": "abba", + "expected": 4 + }, + { + "input": "abcd", + "expected": 0 + } + ] + }, + { + "title": "Sample Test Case 1", + "tests": [ + { + "input": "ifailuhkqq", + "expected": 3 + }, + { + "input": "kkkk", + "expected": 10 + } + ] + }, + { + "title": "Sample Test Case 1", + "tests": [ + { + "input": "cdcd", + "expected": 5 + } + ] + }, + { + "title": "Test case 3", + "tests": [ + { + "input": + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "expected": 166650 + }, + { + "input": + "bbcaadacaacbdddcdbddaddabcccdaaadcadcbddadababdaaabcccdcdaacadcababbabbdbacabbdcbbbbbddacdbbcdddbaaa", + "expected": 4832 + }, + { + "input": + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "expected": 166650 + }, + { + "input": + "cacccbbcaaccbaacbbbcaaaababcacbbababbaacabccccaaaacbcababcbaaaaaacbacbccabcabbaaacabccbabccabbabcbba", + "expected": 13022 + }, + { + "input": + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "expected": 166650 + }, + { + "input": + "bbcbacaabacacaaacbbcaabccacbaaaabbcaaaaaaaccaccabcacabbbbabbbbacaaccbabbccccaacccccabcabaacaabbcbaca", + "expected": 9644 + }, + { + "input": + "cbaacdbaadbabbdbbaabddbdabbbccbdaccdbbdacdcabdbacbcadbbbbacbdabddcaccbbacbcadcdcabaabdbaacdccbbabbbc", + "expected": 6346 + }, + { + "input": + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "expected": 166650 + }, + { + "input": + "babacaccaaabaaaaaaaccaaaccaaccabcbbbabccbbabababccaabcccacccaaabaccbccccbaacbcaacbcaaaaaaabacbcbbbcc", + "expected": 8640 + }, + { + "input": + "bcbabbaccacbacaacbbaccbcbccbaaaabbbcaccaacaccbabcbabccacbaabbaaaabbbcbbbbbaababacacbcaabbcbcbcabbaba", + "expected": 11577 + } + ] + } +] diff --git a/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams-solution-notes.md b/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams-solution-notes.md new file mode 100644 index 0000000..ba6a8f9 --- /dev/null +++ b/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams-solution-notes.md @@ -0,0 +1,30 @@ +# [Sherlock and Anagrams](https://www.hackerrank.com/challenges/sherlock-and-anagrams) + +- Difficulty: `#medium` +- Category: `#ProblemSolvingMedium` `#DictionariesAndHashmaps` `#Strings` + +## About solution + +To answer the question of "how many pairs" of words can be anagrammed +using fragments from adjacent letters of an initial word, two steps are needed: + +1) Obtain all possible fragment candidates to be anagrams, + from each of the possible fragments that can be generated + from adjacent letters of a word. + +2) For each list of candidate anagrams, + calculate all possible permutations and add them up. + The total gives the answer. + +The second part of this problem can be solved with the binomial coefficient formula: + + + +But the entire cost of this formula falls on the "factorial" function. + +In javascript, the factorial quickly reaches results that return large numbers, +in scientific notation, losing precision. +This loss of precision can result in an erroneous result +in the final calculation of permutations. + +To avoid this problem, it is necessary to introduce large number handling using BigInt. diff --git a/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.md b/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.md new file mode 100644 index 0000000..dd9b1bd --- /dev/null +++ b/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.md @@ -0,0 +1,114 @@ +# [Sherlock and Anagrams](https://www.hackerrank.com/challenges/sherlock-and-anagrams) + +- Difficulty: `#medium` +- Category: `#ProblemSolvingMedium` `#DictionariesAndHashmaps` `#Strings` + +Two strings are [http://en.wikipedia.org/wiki/Anagram](anagrams) of each other +if the letters of one string can be rearranged to form the other string. +Given a string, find the number of pairs of substrings of the string that are +anagrams of each other. + +## Example + +`s = mom` + +The list of all anagrammatic pairs is `[m, m]`, `[mo, om]` +at positions `[[0], [2]]`, `[[0, 1], [1, 2]]` respectively. + +## Function Description + +Complete the function sherlockAndAnagrams in the editor below. + +*sherlockAndAnagrams* has the following parameter(s): + +- `string s`: a string + +## Returns + +- `int`: the number of unordered anagrammatic pairs of substrings in **`s`** + +## Input Format + +The first line contains an integer `q`, the number of queries. +Each of the next `q` lines contains a string `s` to analyze. + +## Constraints + +- $ 1 \leq 10 \leq 10 $ +- $ 2 \leq $ lenght of `s` $ \leq 100 $ + +`s` contains only lowercase letters in the range ascii[a-z]. + +## Sample Input 0 + +```text +2 +abba +abcd +``` + +## Sample Output 0 + +```text +4 +0 +``` + +## Explanation 0 + +The list of all anagrammatic pairs is `[a, a]`, `[ab, ba]`, +`[b, b]` and `[abb, bba]` at positions `[[0], [3]]`, `[[0, 1]], [[2, 3]]`, +`[[1], [2]]` and `[[0, 1, 2], [1, 2, 3]]` respectively. + +No anagrammatic pairs exist in the second query as no character repeats. + +## Sample Input 1 + +```text +2 +ifailuhkqq +kkkk +```` + +## Sample Output 1 + +```text +3 +10 +``` + +## Explanation 1 + +For the first query, we have anagram pairs `[i, i]`, `[q, q]` +and `[ifa, fai]` at positions `[[0], [3]]`, `[[8], [9]]` +and `[[0, 1, 2], [1, 2, 3]]` respectively. + +For the second query: + +There are `6` anagrams of the form `[k, k]` at positions `[[0, 1]]`, + `[[0], [2]]`, `[[0], [3]]`, `[[1], [2]]`, `[[1], [3]]` and `[[2], [3]]`. + +There are 3 anagrams of the form `[kk, kk]` at positions `[[0, 1], [1, 2]]`, +`[[0, 1], [2, 3]]` and `[[1, 2], [2, 3]]`. + +There is 1 anagram of the form `[kkk, kkk]` at position `[[0, 1, 2], [1, 2, 3]]`. + +## Sample Input 2 + +```text +1 +cdcd +``` + +## Sample Output 2 + +```text +5 +``` + +## Explanation 2 + +There are two anagrammatic pairs of length `1`: `[c, c]` and `[d, d]`. +There are three anagrammatic pairs of length `2`: +`[cd, dc]`, `[cd, cd]`, `[dc, cd]` at positions +`[[0, 1] [1, 2]]`, `[[0, 1], [2, 3]]`, `[1, 2], [2, 3]` respectively.