Skip to main content
Back to problems
#3844
Medium Algorithms

Longest almost palindromic substring

Two Pointers String Dynamic Programming
22.1% acceptance
Mar 16, 2026
144
11
You are given a string s consisting of lowercase English letters. A substring is almost-palindromic if it becomes a palindrome after removing exactly one character from it. Return the length of the longest almost-palindromic substring in s.

Solution

Rust
Time O(n * m)
Space O(n * m)
LeetCode
solution.rs
impl Solution {
  pub fn almost_palindromic(s: String) -> i32 {
    let s = s.as_bytes();
    let n = s.len();

    // Precompute palindrome table: is_pal[i][j] = true if s[i..=j] is palindrome
    let mut is_pal = vec![vec![false; n]; n];
    for i in 0..n {
      is_pal[i][i] = true;
    }
    for i in 0..n-1 {
      is_pal[i][i+1] = s[i] == s[i+1];
    }
    for len in 3..=n {
      for i in 0..=n-len {
        let j = i + len - 1;
        is_pal[i][j] = s[i] == s[j] && is_pal[i+1][j-1];
      }
    }

    let mut ans = 2; // minimum: any 2-char substring, remove one char -> single char palindrome

    // For each substring s[i..=j] of length >= 2, check if removing one character makes it a palindrome
    // A substring of length L is almost-palindromic if:
    // - Removing the first char: s[i+1..=j] is palindrome
    // - Removing the last char: s[i..=j-1] is palindrome
    // - Or: match from both ends. When mismatch at positions l,r, check if
    //   removing s[l] (i.e., s[l+1..=r] is palindrome) or removing s[r] (i.e., s[l..=r-1] is palindrome)
    //   and the rest was already matched.
    //
    // More efficient approach: for each substring, try two-pointer from outside.
    // Match s[i]==s[j], move inward. On first mismatch at (l,r):
    //   - try skip l: check if s[l+1..=r] is palindrome -> whole thing is almost-palindromic
    //   - try skip r: check if s[l..=r-1] is palindrome -> whole thing is almost-palindromic
    // If no mismatch (already palindrome), we can remove the middle char (or any),
    //   so it's almost-palindromic if length >= 2 (removing one from palindrome of len >= 2
    //   gives palindrome of len >= 1, which is always a palindrome).
    //   Wait - removing one char from a palindrome doesn't always give a palindrome.
    //   E.g., "abba" remove 'a' -> "bba" not palindrome. Remove middle 'b' -> "aba" palindrome.
    //   So for a palindrome, we need to check if removing SOME character makes it a palindrome.
    //   For even palindrome "abba": remove index 1 -> "aba" palindrome. Yes.
    //   For odd palindrome "abcba": remove middle -> "abba" palindrome. Yes.
    //   Actually for any palindrome of length >= 2, removing the middle character (for odd)
    //   or either of the two middle characters (for even) always yields a palindrome.
    //   Proof: palindrome s[0..n-1]. For odd length, remove s[n/2]. Remaining is
    //   s[0..n/2-1] + s[n/2+1..n-1]. Since original is palindrome, s[i] = s[n-1-i].
    //   The remaining string has s[i] at position i for i < n/2, and s[n/2+1+j] = s[n-2-j]
    //   at position n/2 + j. We need remaining[i] = remaining[n-2-i].
    //   For i < n/2: remaining[i] = s[i], remaining[n-2-i]:
    //     if n-2-i >= n/2: remaining[n-2-i] = s[n-1-i] = s[i]. Palindrome holds.
    //   So yes, any palindrome of length >= 2 is almost-palindromic.

    for i in 0..n {
      for j in (i+1..n).rev() {
        let len = j - i + 1;
        if len <= ans as usize { break; }

        // Check if s[i..=j] is almost-palindromic
        let mut l = i;
        let mut r = j;
        let mut found_mismatch = false;
        let mut ok = false;

        while l < r {
          if s[l] == s[r] {
            l += 1;
            r -= 1;
          } else {
            found_mismatch = true;
            // Try removing s[l] or s[r]
            if is_pal[l+1][r] || is_pal[l][r-1] {
              ok = true;
            }
            break;
          }
        }

        if !found_mismatch {
          // It's a palindrome, and len >= 2, so it's almost-palindromic
          ok = true;
        }

        if ok {
          ans = ans.max(len as i32);
        }
      }
    }

    ans
  }
}