0
\$\begingroup\$

Would someone review this code, and confirm that it is indeed recursive? I'm learning computer science and going through different types of algorithms. Right now, I'm focusing on recursion want to ensure that this 'recursive palindrome' method is truly recursive. I've passed six different strings as a test, three that were palindromes and three that were not. The method worked as expected, but again; I'm really looking for a cross-check to ensure this is recursive.

From my understanding, when writing recursive algorithms, the base case should take care of the end of the recursive process. Or, in other words, it should solve the very last sub-problem of the algorithm, after all the recursion has taken place. Please correct me if I am wrong in that thinking.

My observation here is that at some point, the string will end up with 1 character no matter what. Again, the base case should take care of that, but it doesn't. That is why I added the if (str.slice(1, -1).length === 1) check inside of if (firstChar === lastChar). If the method breaks the word down and it is one character and a palindrome, then it will return 'String is a palindrome.' This can be verified when passing 'racecar' as a parameter of the method.

function obtainFirstCharacterOfString(str) {
    const firstChar = str.slice(0, 1);
    return firstChar;
}

function obtainLastCharacterOfString(str) {
    const lastChar = str.slice(-1);
    return lastChar;
}

function recursivePalindrome(str) {
    // Base case
    if (str.length === 0 || str.length === 1) {
        return 'String is not a palindrome';
    }

    const firstChar = obtainFirstCharacterOfString(str);
    const lastChar = obtainLastCharacterOfString(str);

    if (firstChar === lastChar) {
        if (str.slice(1, -1).length === 1) {
            return 'String is a palindrome';
        } else {
            return recursivePalindrome(str.slice(1, -1));
        }
    } else {
        return 'String is not a palindrome';
    }
}
\$\endgroup\$
3
  • 3
    \$\begingroup\$ Please define palindrome! As I understand it, both empty string and single letter word are palindromes... \$\endgroup\$
    – slepic
    Commented Apr 27, 2021 at 5:40
  • 1
    \$\begingroup\$ This doesn't work for palindromes with even number of characets \$\endgroup\$
    – adiga
    Commented Apr 27, 2021 at 5:45
  • 3
    \$\begingroup\$ Definitely write test for at least 0-4 chars long palindromes and at least 2-4 chars long nonpalindromes! \$\endgroup\$
    – slepic
    Commented Apr 27, 2021 at 5:48

1 Answer 1

6
\$\begingroup\$

You really didn't need to include the second snippet that you know is not working.

However, the first snippet is not working correctly either although you thought it does. That's because you didn't write enough tests. The main problem with your implementation is that is treats empty strings and one letter words as non palindromes. But they actually both are palindromes, thats all you need to fix your implementation and actually it would fix the second snippet too.

Now for some more detail about your code.

Don't return human readable text from functions that basically want to return a boolean.

Asking if length is 0 or 1 can be simplified to asking if it is less then 2.

Slicing a string to get character at certain position is quite ineffective, instead you can just access individual chars using square brackets or charAt method. I'd prefer the bracket access because the algorithm can then also tell if arrays are palindromic, not just strings. The slicing of the input to create n strings of length 1 increases the memory required by the algorithm to O(n) although it already is O(n) because of the recursion but it increases the scale anyway.

Also slicing the string to get the input for next recursion step is ineffective and not necessary. Instead you could pass around the entire string and index of currently examined character. The slicing is degrading the time complexity to O(n²) and memory complexity also to O(n²).

For the matter of efficiency I also recommend to avoid recursion entirely but I'll assume recursion is the subject of study here...

function isPalindrome(input) {
  function isPalindromeDetail(first, last) {
    if (last - first < 1) return true
    if (input[first] !== input[last]) return false
    return isPalindromeDetail(first+1, last-1)
  }
  return isPalindromeDetail(0, input.length-1)
}

function testPalindrome(input, expect) {
  const output = isPalindrome(input)
  if (expect && !output) console.error('Expected ', input, ' to be palindrome')
  if (!expect && output) console.error('Expected ', input, ' not to be palindrome')
}

testPalindrome('', true)
testPalindrome('a', true)
testPalindrome('aa', true)
testPalindrome('aba', true)
testPalindrome('abba', true)
testPalindrome('abcba', true)

testPalindrome('ab', false)
testPalindrome('abc', false)
testPalindrome('abbc', false)
testPalindrome('abcab', false)

testPalindrome([1 ,2, 1], true)
testPalindrome([1, 2, 3], false)

PS: I am writing on mobile and haven't tested the code...

PS2: in the above snippet you see that the inner isPalindromeDetail is accessing the input variable from the outer scope rather than having it passed as argument. The same can be done for the first and last variables which only proves that it really doesn't need to be recursive because there is no state that needs to be stacked up. In the recursive form without slicing above we have time complexity O(n) and memory complexity also O(n). In the nonrecirsive form below we have time complexity O(n) and we reduced the memory complexity to O(1) just by not stacking the recursive calls.

function isPalindrome(input) {
  let first = 0
  let last = input.length - 1
  while (first < last) {
    if (input[first] !== input[last]) return false
    ++first
    --last
  }
  return true
}
\$\endgroup\$
2
  • \$\begingroup\$ To confirm my understanding of memory complexity - this is the amount of memory it takes to run the algorithm, correct? In other words, it is the amount of memory that an algorithm consumes when running? Furthermore, I've read that the memory complexity only takes into account the local variables. Is this correct? \$\endgroup\$ Commented May 6, 2021 at 0:35
  • 1
    \$\begingroup\$ @andrewlundy Yes that's true. But also memory allocated on the heap counts. And also each function invocation sieze a few bytes on the implicit stack. That's why recursion depth level is limited, because eventually being too deep consumes the entire stack. \$\endgroup\$
    – slepic
    Commented May 6, 2021 at 3:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.