I am going one-by-one through problems in the "Cracking the Coding Interview". This question is about problem 1.5. One Away. Since I was never good in "algorithming", any suggestions are highly appreciated.
The very best thing you can do for me is suggest a better approach instead of the "straightforward" fragile code I wrote.
Oh, and since it's pretty much an interview question, performance is important too. The solution I got is O(n)
-- bound by the length of the longest string so far (which can be easily fixed by early exit when mismatchCount
exceeds 1
).
Code
/**
* Problem:
* There are three types of edits that can performed on strings:
* insert a character, remove a charachter, or replace a character.
* Given two strings, write a function to check if they are one edit (or zero edits) away.
*
* Example:
* pale, ple -> true
* pales, pale -> true
* pale, bale -> true
* pale, bake -> false
*
* Solution:
* complexity: O(n)
*/
export function areOneAway(leftWord: string, rightWord: string): boolean {
let leftIndex = 0;
let rightIndex = 0;
let mismatchCount = 0;
while (leftIndex < leftWord.length && rightIndex < rightWord.length) {
const leftChar = leftWord[leftIndex];
const rightChar = rightWord[rightIndex];
if (leftChar === rightChar) {
leftIndex++;
rightIndex++;
} else {
const nextLeftChar = leftWord[leftIndex + 1];
const nextRightChar = rightWord[rightIndex + 1];
const leftCharEqualsNextRight = leftChar === nextRightChar;
const rightCharEqualsNextLeft = rightChar === nextLeftChar;
const nextLeftEqualsNextRight = nextLeftChar === nextRightChar;
if (nextLeftEqualsNextRight) {
leftIndex++;
rightIndex++;
} else if (
leftCharEqualsNextRight && rightCharEqualsNextLeft ||
!leftCharEqualsNextRight && !rightCharEqualsNextLeft
) {
return false;
} else if (leftCharEqualsNextRight) {
rightIndex++;
} else if (rightCharEqualsNextLeft) {
leftIndex++;
} else {
throw new Error('Invariant violated');
}
mismatchCount++;
}
}
return ((leftWord.length - leftIndex) + (rightWord.length - rightIndex) + mismatchCount) <= 1;
}
Tests
import { expect } from 'chai';
import { areOneAway } from '../../src/cracking-the-coding-interview/1-5-one-away';
describe(`1-5: OneAway`, () => {
[
{ string1: 'pale', string2: 'ple', expectedResult: true },
{ string1: 'pales', string2: 'pale', expectedResult: true },
{ string1: 'pale', string2: 'bale', expectedResult: true },
{ string1: 'pale', string2: 'bake', expectedResult: false },
{ string1: '', string2: '', expectedResult: true },
{ string1: '', string2: ' ', expectedResult: true },
{ string1: ' ', string2: '', expectedResult: true },
{ string1: '', string2: ' ', expectedResult: false },
{ string1: ' ', string2: '', expectedResult: false },
{ string1: '', string2: 'x ', expectedResult: false },
{ string1: 'x ', string2: '', expectedResult: false },
{ string1: 'a', string2: 'a', expectedResult: true },
{ string1: 'ab', string2: 'a', expectedResult: true },
{ string1: 'abc', string2: 'a', expectedResult: false },
{ string1: 'abc', string2: 'abc', expectedResult: true },
{ string1: 'a', string2: 'ab', expectedResult: true },
{ string1: 'a', string2: 'abc', expectedResult: false },
{ string1: 'xab', string2: 'xa', expectedResult: true },
{ string1: 'xabc', string2: 'xa', expectedResult: false },
{ string1: 'xabc', string2: 'xabc', expectedResult: true },
{ string1: 'xa', string2: 'xab', expectedResult: true },
{ string1: 'xa', string2: 'xabc', expectedResult: false },
].forEach(testCase => {
const { string1, string2, expectedResult } = testCase;
it(`Should return ${expectedResult} for '${string1}' and '${string2}'`, () => {
expect(areOneAway(string1, string2)).to.equal(expectedResult);
});
});
});