diff --git a/DIRECTORY.md b/DIRECTORY.md index 1b2c40b3c8..2ede552665 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -160,6 +160,7 @@ * [NumberOfIslands](Graphs/NumberOfIslands.js) * [PrimMST](Graphs/PrimMST.js) * **Hashes** + * [MD5](Hashes/MD5.js) * [SHA1](Hashes/SHA1.js) * [SHA256](Hashes/SHA256.js) * **Maths** @@ -300,6 +301,7 @@ * [KochSnowflake](Recursive/KochSnowflake.js) * [LetterCombination](Recursive/LetterCombination.js) * [Palindrome](Recursive/Palindrome.js) + * [Partition](Recursive/Partition.js) * [SubsequenceRecursive](Recursive/SubsequenceRecursive.js) * [TowerOfHanoi](Recursive/TowerOfHanoi.js) * **Search** diff --git a/Dynamic-Programming/Abbreviation.js b/Dynamic-Programming/Abbreviation.js new file mode 100644 index 0000000000..06092216b8 --- /dev/null +++ b/Dynamic-Programming/Abbreviation.js @@ -0,0 +1,47 @@ +/** + * @description + * Given two strings, `source` and `target`, determine if it's possible to make `source` equal + * to `target` You can perform the following operations on the string `source`: + * 1. Capitalize zero or more of `source`'s lowercase letters. + * 2. Delete all the remaining lowercase letters in `source`. + * + * Time Complexity: (O(|source|*|target|)) where `|source|` => length of string `source` + * + * @param {String} source - The string to be transformed. + * @param {String} target - The string we want to transform `source` into. + * @returns {Boolean} - Whether the transformation is possible. + * @see https://www.hackerrank.com/challenges/abbr/problem - Related problem on HackerRank. + */ +export const isAbbreviation = (source, target) => { + const sourceLength = source.length + const targetLength = target.length + + // Initialize a table to keep track of possible abbreviations + let canAbbreviate = Array.from({ length: sourceLength + 1 }, () => + Array(targetLength + 1).fill(false) + ) + // Empty strings are trivially abbreviatable + canAbbreviate[0][0] = true + + for (let sourceIndex = 0; sourceIndex < sourceLength; sourceIndex++) { + for (let targetIndex = 0; targetIndex <= targetLength; targetIndex++) { + if (canAbbreviate[sourceIndex][targetIndex]) { + // If characters at the current position are equal, move to the next position in both strings. + if ( + targetIndex < targetLength && + source[sourceIndex].toUpperCase() === target[targetIndex] + ) { + canAbbreviate[sourceIndex + 1][targetIndex + 1] = true + } + // If the current character in `source` is lowercase, explore two possibilities: + // a) Capitalize it (which is akin to "using" it in `source` to match `target`), or + // b) Skip it (effectively deleting it from `source`). + if (source[sourceIndex] === source[sourceIndex].toLowerCase()) { + canAbbreviate[sourceIndex + 1][targetIndex] = true + } + } + } + } + + return canAbbreviate[sourceLength][targetLength] +} diff --git a/Dynamic-Programming/tests/Abbreviation.test.js b/Dynamic-Programming/tests/Abbreviation.test.js new file mode 100644 index 0000000000..86e0f97336 --- /dev/null +++ b/Dynamic-Programming/tests/Abbreviation.test.js @@ -0,0 +1,30 @@ +import { isAbbreviation } from '../Abbreviation.js' + +const expectPositive = (word, abbr) => + expect(isAbbreviation(word, abbr)).toBe(true) +const expectNegative = (word, abbr) => + expect(isAbbreviation(word, abbr)).toBe(false) + +describe('Abbreviation - Positive Tests', () => { + test('it should correctly abbreviate or transform the source string to match the target string', () => { + expectPositive('', '') + expectPositive('a', '') + expectPositive('a', 'A') + expectPositive('abcDE', 'ABCDE') + expectPositive('ABcDE', 'ABCDE') + expectPositive('abcde', 'ABCDE') + expectPositive('abcde', 'ABC') + expectPositive('abcXYdefghijKLmnopqrs', 'XYKL') + expectPositive('abc123', 'ABC') + expectPositive('abc123', 'ABC123') + expectPositive('abc!@#def', 'ABC') + }) +}) + +describe('Abbreviation - Negative Tests', () => { + test('it should fail to abbreviate or transform the source string when it is not possible to match the target string', () => { + expectNegative('', 'A') + expectNegative('a', 'ABC') + expectNegative('aBcXYdefghijKLmnOpqrs', 'XYKLOP') + }) +}) diff --git a/Recursive/Partition.js b/Recursive/Partition.js new file mode 100644 index 0000000000..51465f8e93 --- /dev/null +++ b/Recursive/Partition.js @@ -0,0 +1,39 @@ +/** + * @function canPartition + * @description Check whether it is possible to partition the given array into two equal sum subsets using recursion. + * @param {number[]} nums - The input array of numbers. + * @param {number} index - The current index in the array being considered. + * @param {number} target - The target sum for each subset. + * @return {boolean}. + * @see [Partition Problem](https://en.wikipedia.org/wiki/Partition_problem) + */ + +const canPartition = (nums, index = 0, target = 0) => { + if (!Array.isArray(nums)) { + throw new TypeError('Invalid Input') + } + + const sum = nums.reduce((acc, num) => acc + num, 0) + + if (sum % 2 !== 0) { + return false + } + + if (target === sum / 2) { + return true + } + + if (index >= nums.length || target > sum / 2) { + return false + } + + // Include the current number in the first subset and check if a solution is possible. + const withCurrent = canPartition(nums, index + 1, target + nums[index]) + + // Exclude the current number from the first subset and check if a solution is possible. + const withoutCurrent = canPartition(nums, index + 1, target) + + return withCurrent || withoutCurrent +} + +export { canPartition } diff --git a/Recursive/test/Partition.test.js b/Recursive/test/Partition.test.js new file mode 100644 index 0000000000..929519999a --- /dev/null +++ b/Recursive/test/Partition.test.js @@ -0,0 +1,24 @@ +import { canPartition } from '../Partition' + +describe('Partition (Recursive)', () => { + it('expects to return true for an array that can be partitioned', () => { + const result = canPartition([1, 5, 11, 5]) + expect(result).toBe(true) + }) + + it('expects to return false for an array that cannot be partitioned', () => { + const result = canPartition([1, 2, 3, 5]) + expect(result).toBe(false) + }) + + it('expects to return true for an empty array (0 elements)', () => { + const result = canPartition([]) + expect(result).toBe(true) + }) + + it('Throw Error for Invalid Input', () => { + expect(() => canPartition(123)).toThrow('Invalid Input') + expect(() => canPartition(null)).toThrow('Invalid Input') + expect(() => canPartition(undefined)).toThrow('Invalid Input') + }) +})