Palindrome Matching Question from Google Kick Start Round E 2022

Question:

Google Kick Start Round E just concluded and I could not figure out what edge case I was missing for my implementation of the Palindrome Matching Question.

Problem:
You are given a palindrome string P of length N consisting of only lowercase letters of the English alphabet. Find the shortest non-empty palindrome string Q such that P concatenated with Q forms a palindrome. Formally, the string PQ forms a palindrome.

Input:
The first line of the input gives the number of test cases, T. T test cases follow. Each test case consists of two lines. The first line of each test case contains an integer N denoting the length of the string P. The second line of each test case contains a palindrome string P of length N.

Output: For each test case, output one line containing Case #x: y, where x is the test case number (starting from 1) and y is the non-empty palindrome string Q as described above.

Here is the link to the full question:

https://codingcompetitions.withgoogle.com/kickstart/round/00000000008cb0f5/0000000000ba82c5

My general idea was to iterate over the given palindrome and determine whether I could split the string at the current index into two palindromes. The first instance I could would be the shortest possible non-empty palindromic string that I could concatenate to end of the given palindrome to form a new palindrome. Here was what I wrote that got stuck on the second check:

import fileinput

cases = 0
total_cases = 0
length = 0

def check_palindrome(st,en, s):
    while(st < en):
        if s[st] == s[en]:
            st += 1
            en -=1
        else:
            return False
    return True

def solve(s):
    for i in range(1,len(s) - 1):
        if check_palindrome(i, len(s) - 1, s) and check_palindrome(0, i - 1, s):
            return s[0:i]
    return s

for line in fileinput.input():
    if fileinput.isfirstline():
        total_cases = int(line.strip())
        continue
    elif cases == total_cases:
        break
    else:
        if fileinput.lineno() % 2 == 0:
            length = int(line.strip())
        else:
            s = solve(line.strip())
            cases += 1
            print(f'Case #{cases}: ' + s)

The following was my initial attempt that got a time limit error. For some reason my first try seemed to clear a case that my second try was failing. I honestly can’t seem to find what I did differently:

def check_palindrome(st,en, s):
    while(st < en):
        if s[st] == s[en]:
            st += 1
            en -=1
        else:
            return False
    return True

def solve(s):
    st= 1
    sol = s[0]
    while(st < len(s)):
        if check_palindrome(st, len(s) - 1, s) and check_palindrome(0, len(sol) - 1, sol):
            return sol
        else:
            sol = s[st] + sol
            st += 1
    return sol

I also got the feeling that my approach could be optimized a lot more. If someone could point me in the right direction it would be very much appreciated.

Asked By: genchemmer1234

||

Answers:

Given that both P and Q are palindromes and PQ must be a palindrome, there are these observations:

  • Because PQ is a palindrome, Q is a prefix of P

  • So P is either Q itself, or P is QR, where R must be a palindrome

    • Since QR is a palindrome, Q must be a suffix of R
    • So R is either Q itself, or R is …etc

Conclusion: P must be a repetition of Q.

This means you could apply this approach:

  • Identify all integer divisors of len(P), including len(P) itself: these are the only candidates for len(Q)
  • Iterate this list of integer divisors of P in increasing order, until the corresponding prefix of P is such that P is a repetition of it.
  • That prefix is Q.

NB: there is no need to include a palindrome test for Q, because it follows from the fact that we confirm that Q is both a prefix and a suffix of P, and so because P is given to be a palindrome, Q is its own reverse, i.e. a palindrome.

Implementation

from sympy import divisors  # or use any other efficient implementation

def solve(P):
    lenP = len(P)
    for n in divisors(lenP):
        Q = P[:n]
        if P == Q * (lenP // n):
            return Q
Answered By: trincot