# Longest Palindromic Substring (Linear)

A palindrome is a word, phrase, or sequence that reads the same backward as forward, e.g.,
'madam' or 'racecar'. Even the letter 'x' is considered a palindrome. 

For this Kata, you are given a string ```s```. Write a function that returns the longest _contiguous_ palindromic substring in ```s``` (it could be the entire string). In the event that there are multiple longest palindromic substrings, return the first to occur.

I'm not trying to trick you here:
- You can assume that all inputs are valid strings.
- Only the letters a-z will be used, all lowercase (your solution should, in theory, extend to more than just the letters a-z though). 

**NOTE:** Quadratic asymptotic complexity _(O(N^2))_ or above will __NOT__ work here.

-----

## Examples

### Basic Tests

```
Input: "babad"
Output: "bab"
(Note: "bab" occurs before "aba")
```
```
Input: "abababa"
Output: "abababa"
```
```
Input: "cbbd"
Output: "bb"
```

### Edge Cases

```
Input: "ab"
Output: "a"
```
```
Input: ""
Output: ""
```
-----

## Testing

Along with the example tests given:

 - There are **500** tests using strings of length in range [1 - 1,000]
 - There are **50** tests using strings of length in range [1,000 - 10,000]
 - There are **5** tests using strings of length in range [10,000 - 100,000]
 
All test cases can be passed within 10 seconds, but non-linear solutions will time out every time. _Linear performance is essential_. 

## Good Luck!

-----

This problem was inspired by [this](https://leetcode.com/problems/longest-palindromic-substring/) challenge on LeetCode. Except this is the performance version :^)

```python

USER_FILENAME = "<user_code>"
import sys, json, builtins
import math, heapq, bisect, itertools, functools, collections, statistics, fractions, decimal, operator, array, random, re, string, os, pathlib
try:
    import resource as _res
except Exception:
    _res = None
from collections import Counter, defaultdict, deque, OrderedDict
from functools import lru_cache, reduce
from itertools import accumulate, permutations, combinations, product
from math import gcd, ceil, floor, sqrt
from decimal import Decimal
try:
    from typing import *
except Exception:
    pass
try:
    sys.setrecursionlimit(1_000_000)
except Exception:
    pass
try:
    import site as _site
    try:
        _sys_sites = _site.getsitepackages() if hasattr(_site, "getsitepackages") else []
        for _p in _sys_sites:
            if _p and _p not in sys.path:
                sys.path.append(_p)
    except Exception:
        pass
    try:
        _usp = _site.getusersitepackages()
        if isinstance(_usp, str):
            _usp = [_usp]
        for _p in _usp:
            if _p and _p not in sys.path:
                sys.path.append(_p)
    except Exception:
        pass
except Exception:
    pass

def _disabled_input(*args, **kwargs):
    raise RuntimeError("input() is disabled in callable mode")
builtins.input = _disabled_input

ns = {"__name__": "__judge__"}  # prevent if __name__ == "__main__" blocks
for _k, _v in list(globals().items()):
    if not _k.startswith("_"):
        ns[_k] = _v
_CODE = 'def longest_palindrome(S):\n    """\n    O(n) algorithm to find longest palindrome substring\n    :param S: string to process\n    :return: longest palindrome\n    """\n\n    # Create a copy of array with sentinel chars in between and around\n    # Also include space for starting and ending nodes\n    T = [0] * (2 * (len(S)) + 3)\n\n    # Fill odd indices with sentinel chars evens with real chars\n    sen_char = "@"\n    start_sen = "!"\n    end_sen = "#"\n    for i in range(len(T)):\n        if i == 0:\n            T[i] = start_sen\n        elif i % 2 == 0 and i < len(T) - 1:\n            s_index = (i - 1) // 2\n            T[i] = S[s_index]\n        elif i % 2 == 1 and i < len(T) - 1:\n            T[i] = sen_char\n        else:\n            T[i] = end_sen\n\n    # Also track the expand length around all indices\n    P = [0] * len(T)\n\n    # Track center of largest palin yet\n    # and its right boundary\n    center = right = 0\n\n    # Track largest expansion length\n    # and it index\n    max_len = index = 0\n\n    # Loop through word array to\n    # update expand length around each index\n    for i in range(1, len(T) - 1):\n\n        # Check to see if new palin\n        # around i lies within a bigger one\n        # If so, copy expand length of its mirror\n        mirror = 2 * center - i\n        if i < right:\n            P[i] = min(right - i, P[mirror])\n\n        # Expand around new center\n        # Update expand length at i as needed\n        while T[i + P[i] + 1] == T[i - (P[i] + 1)]:\n            P[i] += 1\n\n        # If we breached previous right boundary\n        # Make i the new center of the longest palin\n        # and update right boundary\n        if i + P[i] > right:\n            right = i + P[i]\n            center = i\n\n        # Update max_len\n        if P[i] > max_len:\n            max_len = P[i]\n            index = i\n\n    t_arr = T[ index - max_len: index + max_len + 1 ]\n    word_arr = [ c for c in t_arr if c != sen_char and c != start_sen and c != end_sen ]\n    word = "".join(word_arr)\n\n    return word'
_co = compile(_CODE, USER_FILENAME, "exec")
exec(_co, ns, ns)

_target = None
_fn_name = 'longest_palindrome'
if _fn_name:
    if "Solution" in ns:
        try:
            _obj = ns["Solution"]()
            if hasattr(_obj, _fn_name):
                _target = getattr(_obj, _fn_name)
        except Exception:
            _target = None
    if _target is None and _fn_name in ns and callable(ns[_fn_name]):
        _target = ns[_fn_name]
# Fallback: if there's exactly one user-defined function, use it
if _target is None:
    _cands = [v for k, v in ns.items() if callable(v) and getattr(v, "__module__", "") in ("__judge__", "__main__")]
    _user_funcs = [c for c in _cands if getattr(c, "__name__", "").startswith("_") is False]
    if len(_user_funcs) == 1:
        _target = _user_funcs[0]
if _target is None:
    raise RuntimeError(f"Could not resolve callable '{_fn_name}'")

_raw = sys.stdin.read().strip()
if _raw:
    _parsed = json.loads(_raw)
    if isinstance(_parsed, dict):
        _kwargs = _parsed.get("kwargs", {})
        _args = _parsed.get("args", [])
    else:
        _args, _kwargs = _parsed, {}
else:
    _args, _kwargs = [], {}
if not isinstance(_args, (list, tuple)):
    _args = [_args]
_res = _target(*_args, **_kwargs)
try:
    sys.stdout.write(repr(_res) + "\n")
except Exception:
    pass

```

Measured memory_bytes: 6874