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 00000000..ba6a8f9e --- /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/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.test.ts b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.test.ts index 91dbdf71..5ec14510 100644 --- a/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.test.ts +++ b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.test.ts @@ -2,51 +2,14 @@ import { describe, expect, it } from '@jest/globals'; import { logger as console } from '../../../logger'; import { sherlockAndAnagrams } from './sherlock_and_anagrams'; - -const TEST_CASES = [ - { - 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 - } - ] - } -]; +import TEST_CASES from './sherlock_and_anagrams.testcases.json'; describe('sherlock_and_anagrams', () => { it('sherlockAndAnagrams test cases', () => { - expect.assertions(5); + expect.assertions(15); TEST_CASES.forEach((testSet) => { - testSet.tests.forEach((test) => { + testSet?.tests.forEach((test) => { const answer = sherlockAndAnagrams(test.input); console.debug( diff --git a/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.testcases.json b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.testcases.json new file mode 100644 index 00000000..f0e7c3ff --- /dev/null +++ b/src/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/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.ts b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.ts index 2ab97cbe..ff0cca56 100644 --- a/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.ts +++ b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.ts @@ -1,12 +1,13 @@ /** * @link Problem definition [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams.md]] + * @see Solution Notes: [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/sherlock_and_anagrams-solution-notes.md]] */ -function factorial(n: number): number { - if (n == 0) { - return 1; - } - return n * factorial(n - 1); +import { logger as console } from '../../../logger'; + +function extraLongFactorials(n: number): bigint { + const rs = [...Array(n)].reduce((a, b, i) => a * BigInt(i + 1), 1n); + return rs; } export function sherlockAndAnagrams(s: string): number { @@ -16,6 +17,9 @@ export function sherlockAndAnagrams(s: string): number { for (let i = 0; i < size; i++) { for (let j = 0; j < size - i; j++) { const substr = s.substring(i, size - j); + console.debug( + `i: ${i}, size: ${size}, size - j: ${size - j} | substr: ${substr}` + ); // Add substrings to a candidate list. // two strings are anagrams if sorted strings are the same. @@ -32,7 +36,8 @@ export function sherlockAndAnagrams(s: string): number { } } - let count = 0; + let total: bigint = BigInt(0); + let q_candidates = 0; // Final Anagram list for (const word of Object.keys(candidates)) { const quantity_of_anagrams = candidates[word].length; @@ -42,15 +47,23 @@ export function sherlockAndAnagrams(s: string): number { delete candidates[word]; } else { // Binomial coefficient: https://en.wikipedia.org/wiki/Binomial_coefficient - count += Math.floor( - factorial(quantity_of_anagrams) / - (factorial(k) * factorial(quantity_of_anagrams - k)) - ); + q_candidates += quantity_of_anagrams; + + const count = + extraLongFactorials(quantity_of_anagrams) / + (extraLongFactorials(k) * + extraLongFactorials(quantity_of_anagrams - k)); + total += count; + + console.debug(`'Partial anagrams of ${word}: ${count}`); } } - console.debug(`filtered candidates: ${count}`); + console.debug( + `'sherlockAndAnagrams(${s}) Filtered # candidates: ${q_candidates}` + ); + console.debug(`'sherlockAndAnagrams(${s}) # anagrams: ${total}`); - return count; + return Number(total); } export default { sherlockAndAnagrams };