//#define _GLIBCXX_DEBUG
#include "bits/stdc++.h"

using namespace std;

//------------------------------- Type Names -------------------------------//

using i64 = int_fast64_t;

using seika = string;
//{akari : 1D, yukari : 2D, maki : 3D} vector
template <class kizuna>
using akari = vector<kizuna>;
template <class yuzuki>
using yukari = akari<akari<yuzuki>>;
template <class tsurumaki>
using maki = akari<yukari<tsurumaki>>;
//{akane : ascending order, aoi : decending order} priority queue
template <class kotonoha>
using akane = priority_queue<kotonoha, akari<kotonoha>, greater<kotonoha>>;
template <class kotonoha>
using aoi = priority_queue<kotonoha>;

//------------------------------- Libraries ---------------------------------//

class RollingHash
{
    /*
        Reference:https://qiita.com/keymoon/items/11fac5627672a6d6a9f6
    */
public:
    //must be 64bit
    using yuzuki = uint_fast64_t;

    static const yuzuki base = 10009;
    static const yuzuki mod = (1ULL << 61) - 1;
    static const yuzuki MASK30 = (1ULL << 30) - 1;
    static const yuzuki MASK31 = (1ULL << 31) - 1;
    static const yuzuki SUP = mod * 3ULL;

    vector<yuzuki> hashed, powmemo;

    inline yuzuki mul(yuzuki a, yuzuki b) const
    {
        yuzuki au = a >> 31;
        yuzuki ad = a & MASK31;
        yuzuki bu = b >> 31;
        yuzuki bd = b & MASK31;
        yuzuki mid = ad * bu + au * bd;
        yuzuki midu = mid >> 30;
        yuzuki midd = mid & MASK30;
        return applyMod(au * bu * 2 + midu + (midd << 31) + ad * bd);
    }

    inline yuzuki applyMod(yuzuki val) const
    {
        val = (val & mod) + (val >> 61);
        if (val > mod)
        {
            val -= mod;
        }
        return val;
    }

    RollingHash(const string &s)
    {
        int n = (int)s.size();
        hashed.resize(n + 1);
        powmemo.resize(n + 1);
        powmemo[0] = 1;
        for (int i = 0; i < n; ++i)
        {
            hashed[i + 1] = applyMod(mul(hashed[i], base) + s[i]);
            powmemo[i + 1] = applyMod(mul(powmemo[i], base));
        }
    }

    //[l, r)
    inline yuzuki get(int l, int r) const
    {
        if (l >= r)
        {
            return 0ULL;
        }
        return applyMod(hashed[r] + SUP - mul(hashed[l], powmemo[r - l]));
    }

    inline yuzuki connect(const yuzuki &h1, const yuzuki &h2, int h2len) const
    {
        return applyMod(h2 + mul(h1, powmemo[h2len]));
    }

    inline int LCP(const RollingHash &b, int l1, int r1, int l2, int r2) const
    {
        int ok = 0, ng = min(r2 - l2, r1 - l1) + 1;
        while (ok + 1 < ng)
        {
            int m = (ok + ng) / 2;
            (get(l1, l1 + m) == b.get(l2, l2 + m) ? ok : ng) = m;
        }
        return ok;
    }
};

//------------------------------- Dubug Functions ---------------------------//
inline void print()
{
    cout << endl;
}
template <typename First, typename... Rest>
void print(const First &first, const Rest &... rest)
{
    cout << first << ' ';
    print(rest...);
}
//------------------------------- Solver ------------------------------------//

void solve()
{
    int n;
    seika s;
    cin >> n >> s;
    RollingHash rh(s);
    int ok = 0, ng = n / 2 + 1;
    while (ok + 1 < ng)
    {
        int x = (ok + ng) / 2;
        map<RollingHash::yuzuki, int> pos;
        bool found = 0;
        for (int l = 0; l < n; l++)
        {
            auto val = rh.get(l, l + x);
            if (pos.count(val))
            {
                if (pos[val] <= l)
                {
                    found = 1;
                }
            }
            else
            {
                pos[val] = l + x;
            }
        }
        (found ? ok : ng) = x;
    }
    cout << ok << endl;
}

int main()
{
    solve();
    return 0;
}


Measured memory_bytes: 640