{-# OPTIONS_GHC -O2 #-}
{-# LANGUAGE BangPatterns          #-}
{-# LANGUAGE CPP                   #-}
{-# LANGUAGE LambdaCase            #-}
{-# LANGUAGE MagicHash             #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE MultiWayIf            #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE TupleSections         #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE ViewPatterns          #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}

import           Control.Applicative
import           Control.Exception
import           Control.Monad
import           Control.Monad.Primitive
import           Control.Monad.ST
import           Control.Monad.State.Strict
import           Data.Bool
import qualified Data.ByteString             as B
import qualified Data.ByteString.Builder     as B
import qualified Data.ByteString.Char8       as C
import qualified Data.ByteString.Internal    as B
import qualified Data.ByteString.Unsafe      as B
import           Data.Char
import qualified Data.Foldable               as F
import           Data.Function
import qualified Data.IntMap.Strict          as IM
import qualified Data.IntSet                 as IS
import qualified Data.List                   as L
import qualified Data.Map.Strict             as M
import           Data.Monoid
import           Data.Ord
import           Data.Primitive.MutVar
import qualified Data.Set                    as S
import           Data.Tuple
import qualified Data.Vector                 as V
import qualified Data.Vector.Generic         as G
import qualified Data.Vector.Generic.Mutable as GM
import qualified Data.Vector.Unboxed         as U
import qualified Data.Vector.Unboxed.Mutable as UM
import           Debug.Trace
import           Foreign                     hiding (void, newArray)
import           GHC.Exts
import qualified System.IO                   as IO
import           Unsafe.Coerce
--
import Data.Array.Base
import Data.Array.ST (runSTUArray)
import Data.Ix
#if MIN_VERSION_GLASGOW_HASKELL(8,0,1,0)
import           Data.Semigroup as Semigroup
#endif

main :: IO ()
main = do
    n <- readLn :: IO Int
    bs <- C.getLine
    print $ solve n bs

solve :: Int -> B.ByteString -> Int
solve n bs = U.maximum . U.generate (n + 1) $ \i -> do
    U.maximum . U.generate (n + 1) $ \j -> do
        if i >= j then 0
        else queryMin rmq i j `min` abs (unsafeAt sa i - unsafeAt sa j)
  where
    !sa = suffixArray bs
    !lcp = lcpArray bs sa
    !rmq = buildRMQ . U.fromList $ elems lcp

-------------------------------------------------------------------------------

floorLog2 :: Int -> Int
floorLog2 x = fromIntegral $ unsafeShiftR y 52 - 1023
  where
    y :: Word64
    y = unsafeCoerce (fromIntegral x :: Double)

#if !MIN_VERSION_GLASGOW_HASKELL(8,0,1,0)
newtype Min a = Min {getMin :: a} deriving (Eq, Ord, Show)
instance (Ord a, Bounded a) => Monoid (Min a) where
    mappend = coerce (min :: a -> a -> a)
    {-# INLINE mappend #-}
    mempty = coerce (maxBound :: a)
    {-# INLINE mempty #-}
#endif

type RMQ a = SparseTable Min a

buildRMQ :: (U.Unbox a, Ord a, Bounded a) => U.Vector a -> RMQ a
buildRMQ = buildSparseTable

queryMin :: (U.Unbox a, Ord a, Bounded a) => RMQ a -> Int -> Int -> a
queryMin = queryST

newtype SparseTable (f :: * -> *) a = SparseTable
    { getSparseTable :: V.Vector (U.Vector a)
    } deriving (Eq, Show)

buildSparseTable :: forall (f :: * -> *) a. (U.Unbox a, Monoid (f a), Coercible (f a) a)
    => U.Vector a -> SparseTable f a
buildSparseTable vec = SparseTable
    . V.scanl' (\acc i -> U.zipWith (coerce op) acc $ U.drop i acc) vec
    $ V.iterateN (floorLog2 $ U.length vec) (*2) 1
  where
    op :: f a -> f a -> f a
    op = mappend

queryST :: forall (f :: * -> *) a. (U.Unbox a, Monoid (f a), Coercible (f a) a)
    => SparseTable f a -> Int -> Int -> a
queryST st l r
    | l < r = (coerce op) x y
    | otherwise = error $ "queryST l: " ++ shows l "r: " ++ show r
  where
    op :: f a -> f a -> f a
    op = mappend
    logStep = floorLog2 $ r - l
    row = V.unsafeIndex (getSparseTable st) logStep
    x = U.unsafeIndex row l
    y = U.unsafeIndex row $ r - unsafeShiftL 1 logStep

rep, rev :: Monad m => Int -> (Int -> m ()) -> m ()
rep !n = U.forM_ $ U.generate n id
rev !n = U.forM_ $ U.iterateN n (subtract 1) (n - 1)
unsafeModify :: (MArray a e m, Ix i) => a i e -> Int -> (e -> e) -> m ()
unsafeModify a i f=unsafeRead a i>>=unsafeWrite a i.f
{-# INLINE unsafeModify #-}

type Index = Int
type Rank = Int
type SuffixArray = UArray Int Int
type LCPArray = UArray Int Int

-------------------------------------------------------------------------------
-- SuffixArray
suffixArray :: B.ByteString -> UArray Int Int
suffixArray bs = runSTUArray $ do
  let !n = B.length bs :: Int
      !countMax = max 0xff (n+1)

  sa    <- newArray (0,n) 0 :: ST s (STUArray s Int Int)
  rank  <- newArray (0,n) 0 :: ST s (STUArray s Int Int)
  tmp   <- newArray (0,n) 0 :: ST s (STUArray s Int Int)
  count <- newArray (0,countMax) 0 :: ST s (STUArray s Int Int)

  rep n $ \i-> do
    unsafeWrite sa i i
    unsafeWrite rank i . fromIntegral $ B.unsafeIndex bs i
  unsafeWrite sa n n
  unsafeWrite rank n 1

  forM_ (takeWhile (<=n) $ iterate (2*) 1) $ \k-> do
    -- sort sa
    rep (countMax+1) $ \i-> do
      unsafeWrite count i 0
    rep (n+1) $ \i-> do
      sai <- unsafeRead sa i
      if sai+k<=n then do
        rankik <- unsafeRead rank (sai+k)
        unsafeModify count rankik (1+)
      else unsafeModify count 0 (1+)
    rep countMax $ \i-> do
      cnti <- unsafeRead count i
      unsafeModify count (i+1) (cnti+)
    rev (n+1) $ \i-> do
      sai <- unsafeRead sa i
      rankik <- if sai + k <= n then do
                   unsafeRead rank (sai+k)
                else return 0
      j <- subtract 1 <$> unsafeRead count rankik
      unsafeWrite count rankik j
      unsafeRead sa i>>=unsafeWrite tmp j

    rep (countMax+1) $ \i-> do
      unsafeWrite count i 0
    rep (n+1) $ \i-> do
      sai <- unsafeRead tmp i
      ranki <- unsafeRead rank sai
      unsafeModify count ranki (1+)
    rep countMax $ \i-> do
      cnti <- unsafeRead count i
      unsafeModify count (i+1) (cnti+)
    rev (n+1) $ \i-> do
      sai <- unsafeRead tmp i
      ranki <- unsafeRead rank sai
      j <- subtract 1 <$> unsafeRead count ranki
      unsafeWrite count ranki j
      unsafeWrite sa j sai

    -- update rank
    sa0 <- unsafeRead sa 0
    unsafeWrite tmp sa0 1
    rep n $ \i-> do
      sai <- unsafeRead sa i
      sai1 <- unsafeRead sa (i+1)
      ranki <- unsafeRead rank sai
      ranki1 <- unsafeRead rank sai1
      if ranki == ranki1 then do
           rankik  <- if sai  + k > n then return (-1)
                      else unsafeRead rank (sai+k)
           ranki1k <- if sai1 + k > n then return (-1)
                      else unsafeRead rank (sai1+k)
           if rankik == ranki1k then do
             unsafeRead tmp sai >>= unsafeWrite tmp sai1
           else do
             unsafeRead tmp sai >>= unsafeWrite tmp sai1 . (1+)
      else do
        unsafeRead tmp sai >>= unsafeWrite tmp sai1 . (1+)

    rep (n+1) $ \i-> do
      unsafeRead tmp i >>= unsafeWrite rank i

  return sa
-------------------------------------------------------------------------------
-- Longest Common Prefix Array

naiveLCP :: B.ByteString -> B.ByteString -> Int
naiveLCP xs ys = go 0
   where
     !n = min (B.length xs) (B.length ys)
     go !i
       | i < n && B.unsafeIndex xs i == B.unsafeIndex ys i = go (i+1)
       | otherwise = i

lcpArray :: B.ByteString -> SuffixArray -> LCPArray
lcpArray bs sa = runSTUArray $ do
  let !n = B.length bs
      rank = array (0,n) . map swap $ assocs sa  :: UArray Int Int
  lcp <- newArray (0,n) 0 :: ST s (STUArray s Int Int)
  let go !i !h
        | i < n = do
            let xs = B.unsafeDrop (i + h) bs
            let ys = B.unsafeDrop (unsafeAt sa (unsafeAt rank i - 1) + h) bs
            let h' = h + naiveLCP xs ys
            unsafeWrite lcp (unsafeAt rank i - 1) h'
            go (i+1) $ max 0 $ h' - 1
        | otherwise = return lcp
  go 0 0

type Parser a = C.ByteString -> Maybe (a, C.ByteString)

parseInt :: Parser Int
parseInt = C.readInt . C.dropWhile isSpace

parseInt2 :: Parser (Int, Int)
parseInt2 = runStateT $
    (,) <$> StateT (C.readInt . C.dropWhile isSpace)
        <*> StateT (C.readInt . B.unsafeTail)

parseInt3 :: Parser (Int, Int, Int)
parseInt3 = runStateT $
    (,,) <$> StateT (C.readInt . C.dropWhile isSpace)
        <*> StateT (C.readInt . B.unsafeTail)
        <*> StateT (C.readInt . B.unsafeTail)

parseInt4 :: Parser (Int, Int, Int, Int)
parseInt4 = runStateT $
    (,,,) <$> StateT (C.readInt . C.dropWhile isSpace)
        <*> StateT (C.readInt . B.unsafeTail)
        <*> StateT (C.readInt . B.unsafeTail)
        <*> StateT (C.readInt . B.unsafeTail)

Measured memory_bytes: 2172