Start learning free

LESSON 458 OF 540 · DYNAMIC PROGRAMMING

Longest Palindromic Substring in Scala: Expand Around Center

Practice O(n2) Read the full explanation

You are given a string s. Return its longest palindromic substring. If several substrings have that greatest length, return the one that starts first, furthest to the left.

A palindrome reads the same forward and backward, like "racecar" or "abba". A substring is a run of consecutive characters of s: "bcd" is a substring of "abcde", but "ace" is not.

 

Example 1:

Input: s = "babad"
Output: "bab"
Explanation: "aba" is a palindrome of length 3 too, but "bab" starts first.
No substring of length 4 or 5 is a palindrome.

Example 2:

Input: s = "cbbd"
Output: "bb"
Explanation: A palindrome of even length, like "bb", has its middle between two characters.

Example 3:

Input: s = "abacdfgdcaba"
Output: "aba"
Explanation: "aba" appears at the start and at the end, and the first one is returned.
"abacd" read backward is "dcaba", which also appears in s, but neither of them is a palindrome.

 

Constraints:

  • 1 <= s.length <= 1000
  • s is made of English letters and digits only. Uppercase and lowercase letters are different characters: "Aa" is not a palindrome.

Note:

Your algorithm should run in O(n2) time and use O(1) extra space besides the returned string, where n is the length of s.



Checking every substring on its own takes O(n3): about n2/2 substrings, each up to n characters long. Look at palindromes from the middle instead. Every palindrome has a center: its middle character when its length is odd, or the gap between its two middle characters when the length is even. A string of n characters has 2n - 1 centers: n characters and n - 1 gaps.

From a center, expand: start with two positions left and right (the same character, or two neighbours), and move them one step outward while both are inside s and their characters are equal. When they stop, the palindrome between them has right - left - 1 characters, and it is the longest one around that center. A helper that does this and returns the length, called for the centers (i, i) and (i, i + 1) of every i, finds the longest palindrome at each center.
A palindrome of length length centered at i starts at i - (length - 1) / 2, rounded down. Keep the best start and length, and replace them only by a strictly longer palindrome: the centers go from left to right, so the first of the longest ones stays.

There are 2n - 1 centers, and each expansion takes at most n / 2 steps: O(n2) time. Only a few numbers are kept: O(1) extra space, besides the returned string.
A table dp[i][j] telling whether s[i..j] is a palindrome works too, in the same time but with O(n2) space. Manacher's algorithm even finds the answer in O(n), but it goes beyond this lesson.
STEP 1 OF 14
    Ctrl + Enter to check

    SCALA · DYNAMIC PROGRAMMING

    Longest Palindromic Substring in Scala, explained

    Problem overview

    The method longestPalindrome(s: String): String returns the longest palindromic substring of s. A palindrome reads the same forward and backward, such as "racecar" or "abba". A substring is a run of consecutive characters, so "bcd" is a substring of "abcde" and "ace" is not. When several palindromes share the greatest length, the one that starts first, furthest to the left, wins. Uppercase and lowercase letters are different characters, so "Aa" is not a palindrome.

    For "babad" the answer is "bab". The substring "aba" has the same length, but "bab" starts earlier. For "cbbd" the answer is "bb", an even-length palindrome. For "abacdfgdcaba" the answer is "aba", the copy at the start.

    Approach: grow a palindrome from its middle

    Checking every substring separately costs O(n³): about n²/2 substrings, each up to n characters long. Looking at palindromes from the middle is cheaper. Every palindrome has a center: its middle character when its length is odd, or the gap between its two middle characters when the length is even. A string of n characters has 2n - 1 centers, which are n characters and n - 1 gaps.

    From a center, expand. Start with two positions left and right, either the same character or two neighbours, and move them one step outward while both are inside the string and the characters match. When they stop, the palindrome lies strictly between them and has right - left - 1 characters. It is the longest palindrome around that center.

    The loop visits every index i and expands two centers: (i, i) for odd lengths and (i, i + 1) for even lengths. For "babad" the lengths found are:

    i=0 odd=1 even=0
    i=1 odd=3 even=0
    i=2 odd=3 even=0
    i=3 odd=1 even=0
    i=4 odd=1 even=0
    

    The center at index 1 gives the first palindrome of length 3, "bab". The center at index 2 gives another of length 3, "aba", but it is not longer, so it does not replace the first. For "cbbd" the only even palindrome is at i = 1, where even is 2.

    The method remembers only the best start and the best length, and cuts out the substring once at the end.

    Step by step

    1. Create bestStart and bestLen as vars, both 0. They record where the best palindrome so far starts and how long it is.
    2. Loop over every position with i <- 0 until s.length. Each i brings two centers, the character s(i) and the gap after it.
    3. Call expand(s, i, i) and keep the result in odd: the longest palindrome whose middle is s(i). The lesson writes the helper at the end and assumes it exists for now.
    4. Call expand(s, i, i + 1) and keep the result in even. If the two characters differ, or i is the last index, it is 0.
    5. Take math.max(odd, even) as length, the longest palindrome centered at i of either kind.
    6. Compare length > bestLen. The comparison is strict, so a palindrome just as long as the best one, which starts further right, never replaces it.
    7. Set bestStart to i - (length - 1) / 2. An odd palindrome has (length - 1) / 2 characters before its middle s(i). An even one has length / 2 - 1 before its left middle character, which is the same number once the division rounds down.
    8. Set bestLen to length.
    9. Return s.substring(bestStart, bestStart + bestLen) as the last expression. substring leaves out its end index, so it takes exactly bestLen characters.
    10. Write expand as a method of the same class, with s, left and right as parameters and an Int result.
    11. Copy the parameters into vars l and r. Scala parameters cannot be reassigned.
    12. Loop while l >= 0 && r < s.length && s(l) == s(r). The first two tests keep both ends inside the string, and && stops at the first false one, so characters are compared only when both indexes are valid.
    13. Move outward: l -= 1 and r += 1.
    14. Return r - l - 1. The loop stops one step past the palindrome on each side, so it runs from l + 1 to r - 1.

    The solution in Scala

    class Solution {
        def longestPalindrome(s: String): String = {
            var bestStart = 0
            var bestLen = 0
            for (i <- 0 until s.length) {
                val odd = expand(s, i, i)
                val even = expand(s, i, i + 1)
                val length = math.max(odd, even)
                if (length > bestLen) {
                    bestStart = i - (length - 1) / 2
                    bestLen = length
                }
            }
            s.substring(bestStart, bestStart + bestLen)
        }
    
        def expand(s: String, left: Int, right: Int): Int = {
            var l = left
            var r = right
            while (l >= 0 && r < s.length && s(l) == s(r)) {
                l -= 1
                r += 1
            }
            r - l - 1
        }
    }
    

    The Scala details worth a closer look:

    • Strings are indexed with parentheses. s(l) gives a Char, and == on two Chars compares their values.
    • Parameters are values. left and right cannot be changed, which is why expand copies them. The copies get shorter names, l and r, because they are separate variables from the parameters.
    • Integer division truncates. (length - 1) / 2 drops the remainder. Since length is at least 1 whenever it is used, the numerator is never negative, so truncating is the same as rounding down.
    • Methods can call each other in any order. longestPalindrome calls expand, which is defined below it in the class, by its plain name.

    Complexity

    Time: O(n²). There are 2n - 1 centers, and each expansion takes at most about n / 2 steps. A string of one repeated letter is the worst case.

    Space: O(1) extra, besides the returned string. The method keeps a handful of integers.

    A table that tells whether each substring is a palindrome gives the same time with O(n²) space. Manacher's algorithm finds the answer in O(n) but goes beyond this lesson.

    Edge cases and common mistakes

    • A single character. "a" gives "a". Its odd expansion has length 1, and the even expansion starts out of range and returns 0.
    • No repeated characters. "ab" gives "a", and "ac" gives "a": every palindrome has length 1, and the first one wins.
    • Case matters. "Aa" gives "A".
    • Only odd centers. A version that expands only (i, i) returns "c" for "cbbd" and misses "bb".
    • >= instead of >. The later palindrome would replace an equally long earlier one, and "babad" would give "aba" instead of "bab".
    • Wrong start for even palindromes. The formula i - (length - 1) / 2 works for both kinds because the division rounds down. A formula such as i - length / 2 fits odd lengths but starts one position too far left for even ones.
    • Off-by-one in the length. The expansion stops one step too far on both sides, which is why the length is r - l - 1 and not r - l + 1.
    • A whole-string palindrome. "abba" and "racecar" are returned whole.

    Testing

    • "babad" gives "bab".
    • "cbbd" gives "bb".
    • "abacdfgdcaba" gives "aba".
    • "a" gives "a".
    • "ab" gives "a".
    • "Aa" gives "A".
    • "racecar" gives "racecar".
    • "forgeeksskeegfor" gives "geeksskeeg".
    • "12321x" gives "12321".
    Keep going with Scala500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free