Skip to content

Instantly share code, notes, and snippets.

@cardin
Created June 10, 2020 02:28
Show Gist options
  • Select an option

  • Save cardin/9257ef3714ec4746f5aa49fa239548cd to your computer and use it in GitHub Desktop.

Select an option

Save cardin/9257ef3714ec4746f5aa49fa239548cd to your computer and use it in GitHub Desktop.
Check if input string is a palindrome, if character deletions are allowed
/**
Copyright 2020 Cardin Lee
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
Palindrome
Task: Determine if a given string is a palindrome, if a certain number of character deletions are allowed.
Procedure:
1. Put 1 pointer to the start of the string, 1 pointer to the end of the string
2. A counter has the value 2, e.g. the number of characters that can be deleted. When counter reaches -1, terminate with failure.
3. Generate a permutation of steps that we can advance the pointer while within the counter limits.
4. Iterate through the permutation and the string.
*/
/** @enum {number} */
const DIR = {
ADVANCE: 1,
RETREAT: -1
};
class Config {
/**
*
* @param {boolean} longestPalindrome
* If true, it favors nearest longest palindrome.
* If false, it favors nearest palindrome.
* Whether it's really the longest palindrome, I doubt so.
*
* @param {number} delAllowed
*/
constructor(longestPalindrome = true, delAllowed = 2) {
this.longestPalindrome = longestPalindrome;
this.delAllowed = delAllowed;
}
}
class Ptr {
/**
*
* @param {string} str
*/
constructor(str) {
this.l = -1;
this.r = str.length;
}
hasEnded() {
// R and L is adjacent (diff 1)
// R and L is identical (diff 0)
return this.r - this.l <= 1;
}
/**
*
* @param {string} str
*/
compareChars(str) {
return str[this.l] == str[this.r];
}
/**
*
* @param {number} offsetL
* @param {number} offsetR
* @param {number} [dir]
*/
adjust(offsetL, offsetR, dir = DIR.ADVANCE) {
// note that r is opposite dir
this.l += offsetL * dir;
this.r += offsetR * -dir;
}
}
/**
* Runtime: O (v log v), where v = val
*
* @param {number} val
* @param {boolean} longestPalindrome
* @returns All (ordered) permutations of number-pairs that sum to `val`
*/
function permutePairs(val, longestPalindrome) {
let permute = [];
for (let i = 0; i <= val; ++i) {
for (let j = 0; (j + i) <= val; ++j) {
permute.push([i, j]);
}
}
if (longestPalindrome) {
permute.sort((a, b) => (a[0] + a[1]) - (b[0] + b[1]));
}
return permute;
}
// console.log(permutePairs(4, true).join(" | ")); // debug permutePairs()
/**
* Runtime: O (d log d), where d = delRemain
*
* ptr will be updated to the last-known correct palindrome position
*
* @param {string} str
* @param {Ptr} ptr
* @param {Iterable<number[]>} delPairs
*/
function validate(str, ptr, delPairs) {
for (let [i, j] of delPairs) {
ptr.adjust(i, j);
if (ptr.compareChars(str)) {
return i + j;
} else {
ptr.adjust(i, j, DIR.RETREAT);
}
}
return -1;
}
/**
* Runtime: O(n), where n is the length of palindromeArr
* @param {string[]} palindromeArr
* @param {Ptr} ptr
*/
function createPalindrome(palindromeArr, ptr) {
let i = (ptr.l == ptr.r) ? palindromeArr.length - 2 : palindromeArr.length - 1;
for (; i >= 0; --i) {
palindromeArr.push(palindromeArr[i]);
}
return palindromeArr.join("");
}
/**
* Runtime: O(nd log d), where n is input.length, d is cfg.delAllowed
* aka O(n) if d is constant
*
* @param {string} input
* @param {Config} cfg
*/
function run(input, cfg) {
const ptr = new Ptr(input);
let delRemain = cfg.delAllowed;
let palindromeArr = [];
// O(d log d)
let delPermutePairs = permutePairs(cfg.delAllowed, cfg.longestPalindrome);
// O(n) * O(d log d)
while (!ptr.hasEnded()) {
ptr.adjust(1, 1);
// O(d log d)
const result = validate(input, ptr, delPermutePairs);
if (result == -1) {
break;
} else {
palindromeArr.push(input[ptr.l]);
delRemain -= result;
// O(d log d)
delPermutePairs = delPermutePairs.filter(x => x[0] + x[1] <= delRemain);
}
}
if (ptr.hasEnded()) {
// O(n)
const palindrome = createPalindrome(palindromeArr, ptr);
console.log(input, "is palindrome", palindrome);
return palindrome;
} else {
console.log(input, "is not palindrome");
return null;
}
}
module.exports.checkPalindrome = run;
let cfg = new Config(false, 2);
console.assert(run("abcba", cfg) == "abcba");
console.assert(run("abczba", cfg) == "abcba");
console.assert(run("abczzba", cfg) == "abcba");
console.assert(run("abczzzba", cfg) == "abzzzba");
console.assert(run("abwcfba", cfg) == "abwba");
console.assert(run("awbcbfa", cfg) == "abcba");
console.assert(!run("awefwefwwbcbfa", cfg));
cfg = new Config(true, 2);
console.assert(run("abcba", cfg) == "abcba");
console.assert(run("abczba", cfg) == "abcba");
console.assert(run("abczzba", cfg) == "abzzba");
console.assert(run("abczzzba", cfg) == "abzzzba");
console.assert(run("abwcfba", cfg) == "abwba");
console.assert(run("awbcbfa", cfg) == "abcba");
console.assert(!run("awefwefwwbcbfa", cfg));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment