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') + }) +})