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.
CodePudding user response:
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