From 7aae7450482be7a139eea7f5a644f3b211b4e545 Mon Sep 17 00:00:00 2001 From: Gonzalo Diaz Date: Fri, 19 Jul 2024 01:05:06 -0400 Subject: [PATCH] =?UTF-8?q?[Hacker=20Rank]=20Interview=20Preparation=20Kit?= =?UTF-8?q?:=20Dictionaries=20and=20Hashmaps:=20Count=20Triplets.=20Solved?= =?UTF-8?q?=20=E2=9C=85.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../count_triplets_1-solution-notes.md | 113 ++++++++++++++++++ .../count_triplets_1.md | 99 +++++++++++++++ .../count_triplets_1_bruteforce.js | 26 ++++ .../count_triplets_1_bruteforce.test.js | 47 ++++++++ .../count_triplets_1_optimized.test.js | 49 ++++++++ .../count_triplets_1_optmized.js | 38 ++++++ .../count_triplets_1_testcases.json | 26 ++++ 7 files changed, 398 insertions(+) create mode 100644 docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1-solution-notes.md create mode 100644 docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1.md create mode 100644 src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_bruteforce.js create mode 100644 src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_bruteforce.test.js create mode 100644 src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_optimized.test.js create mode 100644 src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_optmized.js create mode 100644 src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_testcases.json diff --git a/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1-solution-notes.md b/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1-solution-notes.md new file mode 100644 index 0000000..863e433 --- /dev/null +++ b/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1-solution-notes.md @@ -0,0 +1,113 @@ +# [Dictionaries and Hashmaps: Count Triplets](https://www.hackerrank.com/challenges/count-triplets-1) + +- Difficulty: `#medium` +- Category: `#ProblemSolvingIntermediate` + +## Failed tries + +### Introducing optimizations and cache + +This solution is based on the idea of traversing each possible case, +similar to brute force, but introducing a cache mechanism +for each case found and cutting the number of times it would have to be traversed. + +This has following problems: + +1) The problem is that it preserves the complexity of O(n^3) (same as brute force) + +2) "Cognitive Complexity" (measured by sonarlint) it is very high. +This due to the number of nesting. + +3) Initially, the innermost calculation assumes that the values in the array +"should" be ordered, that is, it expects that the values to the right of the +currently evaluated value would be larger. +With this idea in mind, an initial ordering of values was introduced. +But the problem requires that the values be in positions i < j < k. +That is why initially an ordering of the input was executed, +but such ordering introduces unexpected possibilities. In an unordered list, +it can produce overcounts of favorable cases. + +```python +def count_triplets_with_cache_and_cuts(arr: list[int], r: int) -> int: + + # arrc = arr[:] + # arrc = sorted(arrc) + size = len(arr) + + cache: dict[tuple[int, int, int], bool] = {} + counter = 0 + + i_resume = True + i = 0 + while i < size - 2 and i_resume: + j_resume = True + j = i + 1 + while j < size - 1 and j_resume: + if arr[j] > r * arr[i]: + j_resume = False + + k_resume = True + k = j + 1 + while k < size and k_resume: + if arr[k] > r * arr[j]: + k_resume = False + + triplet = (arr[i], arr[j], arr[k]) + + if triplet in cache and cache[triplet]: + counter += 1 + else: + if r * arr[i] == arr[j] and r * arr[j] == arr[k]: + cache[triplet] = True + counter += 1 + else: + cache[triplet] = False + + k += 1 + j += 1 + i += 1 + + return counter +``` + +### Reducing complexity + +This solutions reduce complexity to O(N), but has the same problem +with unordered lists as the previous case. + +```python +def count_triplets_doenst_work_with_unsorted_input(arr: list[int], r: int) -> int: + count: int = 0 + + if len(arr) < 3: + return count + + counter = Counter(arr) + size = len(counter) + limit = size - 2 + + i = 0 + for k, v in counter.items(): + knext = k * r + knext_next = k * r * r + + if i < limit and knext in counter and knext_next in counter: + next_elem_cnt = counter[knext] + next_next_elem_cnt = counter[knext_next] + count += v * (next_elem_cnt * next_next_elem_cnt) + elif r == 1: + count += math.factorial(v) // (math.factorial(3) * math.factorial(v - 3)) + + i += 1 + + return count +``` + +## Working solution + +This solution in O(N), is based on considering that each +number analyzed is in the center of the triplet and asks +"how many" possible cases there are on the left and +right to calculate how many possible triplets are formed. + +- Source: [Hackerrank - Count Triplets Solution](https://www.thepoorcoder.com/hackerrank-count-triplets-solution/) diff --git a/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1.md b/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1.md new file mode 100644 index 0000000..53f95a8 --- /dev/null +++ b/docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1.md @@ -0,0 +1,99 @@ +# [Dictionaries and Hashmaps: Count Triplets](https://www.hackerrank.com/challenges/count-triplets-1) + +- Difficulty: `#medium` +- Category: `#ProblemSolvingIntermediate` + +You are given an array and you need to find number of +tripets of indices (i, j, k) such that the elements at +those indices are in geometric progression for a given +common ratio `r` and $ i < j < k $. + +## Example + +`arr = [1, 4, 16, 64] r = 4` + +There are `[1, 4, 16]` and `[4, 16, 64]` at indices (0, 1, 2) and (1, 2, 3). +Return `2`. + +## Function Description + +Complete the countTriplets function in the editor below. + +countTriplets has the following parameter(s): + +- `int arr[n]`: an array of integers +- `int r`: the common ratio + +## Returns + +- `int`: the number of triplets + +## Input Format + +The first line contains two space-separated integers `n` and `r`, +the size of `arr` and the common ratio. +The next line contains `n` space-seperated integers `arr[i]`. + +## Constraints + +- $ 1 \leq n \leq 10^5 $ +- $ 1 \leq r \leq 10^9 $ +- $ 1 \leq arr[i] \leq 10^9 $ + +## Sample Input 0 + +```text +4 2 +1 2 2 4 +``` + +## Sample Output 0 + +```text +2 +``` + +## Explanation 0 + +There are `2` triplets in satisfying our criteria, +whose indices are (0, 1, 3) and (0, 2, 3) + +## Sample Input 1 + +```text +6 3 +1 3 9 9 27 81 +``` + +## Sample Output 1 + +```text +6 +``` + +## Explanation 1 + +The triplets satisfying are index +`(0, 1, 2)`, `(0, 1, 3)`, `(1, 2, 4)`, `(1, 3, 4)`, `(2, 4, 5)` and `(3, 4, 5)`. + +## Sample Input 2 + +```text +5 5 +1 5 5 25 125 +``` + +## Sample Output 2 + +```text +4 +``` + +## Explanation 2 + +The triplets satisfying are index +`(0, 1, 3)`, `(0, 2, 3)`, `(1, 2, 3)`, `(2, 3, 4)`. + +## Appendix + +[Solution notes](count_triplets_1-solution-notes.md) diff --git a/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_bruteforce.js b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_bruteforce.js new file mode 100644 index 0000000..edca060 --- /dev/null +++ b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_bruteforce.js @@ -0,0 +1,26 @@ +/** + * @link Problem definition [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1.md]] + * @see Solution Notes: [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1-solution-notes.md]] + */ +import { logger as console } from '../../../logger.js'; + +export function countTriplets(arr, ratio) { + const size = arr.length; + let counter = 0; + + for (let i = 0; i < size - 2; i++) { + for (let j = i + 1; j < size - 1; j++) { + for (let k = j + 1; k < size; k++) { + console.debug(`${arr[i]}, ${arr[j]}, ${arr[k]}`); + + if (ratio * arr[i] === arr[j] && ratio * arr[j] === arr[k]) { + counter += 1; + } + } + } + } + + return counter; +} + +export default { countTriplets }; diff --git a/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_bruteforce.test.js b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_bruteforce.test.js new file mode 100644 index 0000000..590330e --- /dev/null +++ b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_bruteforce.test.js @@ -0,0 +1,47 @@ +import { describe, expect, it } from '@jest/globals'; +import { logger as console } from '../../../logger.js'; + +import { countTriplets } from './count_triplets_1_bruteforce.js'; + +const SMALL_TEST_CASES = [ + { + title: 'Sample Test Case 0', + input: [1, 2, 2, 4], + r: 2, + expected: 2 + }, + { + title: 'Sample Test Case 1', + input: [1, 3, 9, 9, 27, 81], + r: 3, + expected: 6 + }, + { + title: 'Sample Test Case 1 (unsorted)', + input: [9, 3, 1, 81, 9, 27], + r: 3, + expected: 1 + }, + { + title: 'Sample Test Case 12', + input: [1, 5, 5, 25, 125], + r: 5, + expected: 4 + } +]; + +describe('count_triplets_1', () => { + it('countTriplets test cases', () => { + expect.assertions(4); + + SMALL_TEST_CASES.forEach((test) => { + const answer = countTriplets(test.input, test.r); + + console.debug( + `countTriplets(${test.input}, ${test.r}) solution found: ${answer}` + ); + + expect(answer).toStrictEqual(test.expected); + }); + }); +}); diff --git a/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_optimized.test.js b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_optimized.test.js new file mode 100644 index 0000000..78890b9 --- /dev/null +++ b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_optimized.test.js @@ -0,0 +1,49 @@ +import { describe, expect, it } from '@jest/globals'; +import { logger as console } from '../../../logger.js'; + +import { countTriplets } from './count_triplets_1_optmized.js'; +import SMALL_TEST_CASES from './count_triplets_1_testcases.json'; + +const BIG_TEST_CASES = [ + { + title: 'Sample Test Case 2', + input: [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ], + r: 1, + expected: 161700 + } +]; + +describe('count_triplets_1 (optimized)', () => { + it('countTriplets small test cases', () => { + expect.assertions(4); + + SMALL_TEST_CASES.forEach((test) => { + const answer = countTriplets(test.input, test.r); + + console.debug( + `countTriplets(${test.input}, ${test.r}) solution found: ${answer}` + ); + + expect(answer).toStrictEqual(test.expected); + }); + }); + + it('countTriplets big test cases', () => { + expect.assertions(1); + + BIG_TEST_CASES.forEach((test) => { + const answer = countTriplets(test.input, test.r); + + console.debug( + `countTriplets(${test.input}, ${test.r}) solution found: ${answer}` + ); + + expect(answer).toStrictEqual(test.expected); + }); + }); +}); diff --git a/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_optmized.js b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_optmized.js new file mode 100644 index 0000000..bef443a --- /dev/null +++ b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_optmized.js @@ -0,0 +1,38 @@ +/** + * @link Problem definition [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1.md]] + * @see Solution Notes: [[docs/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1-solution-notes.md]] + */ + +export function countTriplets(arr, ratio) { + let triplets = 0; + + const aCounter = arr.reduce((accumulator, entry) => { + if (entry in accumulator) { + accumulator[entry] += 1; + } else { + accumulator[entry] = 1; + } + return accumulator; + }, {}); + + const bCounter = {}; + + arr.forEach((x) => { + const j = Math.floor(x / ratio); + const k = x * ratio; + aCounter[x] -= 1; + if (bCounter[j] && aCounter[k] && x % ratio === 0) { + triplets += bCounter[j] * aCounter[k]; + } + + if (x in bCounter) { + bCounter[x] += 1; + } else { + bCounter[x] = 1; + } + }); + + return triplets; +} + +export default { countTriplets }; diff --git a/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_testcases.json b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_testcases.json new file mode 100644 index 0000000..a2df241 --- /dev/null +++ b/src/hackerrank/interview_preparation_kit/dictionaries_and_hashmaps/count_triplets_1_testcases.json @@ -0,0 +1,26 @@ +[ + { + "title": "Sample Test Case 0", + "input": [1, 2, 2, 4], + "r": 2, + "expected": 2 + }, + { + "title": "Sample Test Case 1", + "input": [1, 3, 9, 9, 27, 81], + "r": 3, + "expected": 6 + }, + { + "title": "Sample Test Case 1 (unsorted)", + "input": [9, 3, 1, 81, 9, 27], + "r": 3, + "expected": 1 + }, + { + "title": "Sample Test Case 12", + "input": [1, 5, 5, 25, 125], + "r": 5, + "expected": 4 + } +]