#include "bits/stdc++.h"
#define _overload3(_1,_2,_3,name,...) name
#define _rep(i,n) repi(i,0,n)
#define repi(i,a,b) for(int i=int(a),i##_len=(b);i<i##_len;++i)
#define MSVC_UNKO(x) x
#define rep(...) MSVC_UNKO(_overload3(__VA_ARGS__,repi,_rep,_rep)(__VA_ARGS__))
#define all(c) c.begin(),c.end()
#define mp make_pair
#define write(x) cout<<(x)<<'\n'
using namespace std; typedef long long ll; template<class T>using vv = vector<vector<T>>;
template<class T>auto vvec(int n, int m, T v) { return vv<T>(n, vector<T>(m, v)); }
template<class T, class U>bool chmax(T& a, U&& b) { return a < b ? a = b, 1 : 0; }
template<class T, class U>bool chmin(T& a, U&& b) { return b < a ? a = b, 1 : 0; }
constexpr int INF = 1 << 29, MOD = int(1e9) + 7; constexpr ll LINF = 1LL << 60;
struct aaa { aaa() { cin.tie(0); ios::sync_with_stdio(0); cout << fixed << setprecision(10); }; }aaaa;

struct CharToInt { constexpr int operator ()(char c) const { return (int)c; } };
template<const int N_Char = 128, class Converter = CharToInt>
struct SuffixArrayByInducedSorting {
    const Converter convert = Converter();
    vector<int> S, sa, lcp, rank;

    template<class String> void build_sa(String&& s) {
        int n = s.size(); S.resize(n + 1);
        for (int i = 0; i < n; ++i) S[i] = convert(s[i]);
        s[n] = 0; sa.resize(n + 1);
        impl(S.begin(), n + 1, sa.begin(), N_Char);
    }
    void build_lcp() {
        rank.resize(S.size()); lcp.resize((int)S.size() - 1); lcp[0] = 0;
        for (int i = 0; i < (int)S.size(); ++i) rank[sa[i]] = i;
        for (int i = 0, h = 0; i < (int)S.size() - 1; ++i) {
            if (h > 0) h--;
            for (int j = sa[rank[i] - 1]; j + h < (int)S.size() - 1 && i + h < (int)S.size() - 1; ++h) {
                if (S[j + h] != S[i + h]) break;
            }
            lcp[rank[i] - 1] = h;
        }
    }

private:
    using vi = vector<int>;
    void impl(vi::iterator s, int n, vi::iterator sa, int bucketSize) {
        vector<bool> ls(n + 1); ls[n - 2] = 0; ls[n - 1] = 1;
        for (int i = n - 3; i >= 0; i--) ls[i] = s[i] < s[i + 1] || (s[i] == s[i + 1] && ls[i + 1]);

        vi bucket(bucketSize + 1); getBuckets(bucket, bucketSize, s, n, true);
        fill(sa, sa + n, -1);
        for (int i = 1; i < n; ++i) if (isLMS(ls, i)) sa[--bucket[s[i]]] = i;
        induce_sa_l(s, n, sa, ls, bucket, bucketSize);
        induce_sa_s(s, n, sa, ls, bucket, bucketSize);

        int n1 = 0;
        for (int i = 0; i < n; ++i) if (isLMS(ls, sa[i])) sa[n1++] = sa[i];
        fill(sa + n1, sa + n, -1); int name = 0, prev = -1;
        for (int i = 0; i < n1; ++i) {
            int pos = sa[i]; bool diff = false;
            for (int d = 0; d < n; ++d)
                if (prev == -1 || s[pos + d] != s[prev + d] || ls[pos + d] != ls[prev + d]) { diff = true; break; }
                else if (d > 0 && (isLMS(ls, pos + d) || isLMS(ls, prev + d))) break;
            if (diff) { name++; prev = pos; }
            pos /= 2; sa[n1 + pos] = name - 1;
        }
        for (int i = n - 1, j = n - 1; i >= n1; --i) if (sa[i] >= 0) sa[j--] = sa[i];

        vi::iterator s1 = sa + n - n1;
        if (name < n1) impl(s1, n1, sa, name - 1);
        else for (int i = 0; i < n1; ++i) sa[s1[i]] = i;

        getBuckets(bucket, bucketSize, s, n, true);
        for (int i = 1, j = 0; i < n; ++i) if (isLMS(ls, i)) s1[j++] = i;
        for (int i = 0; i < n1; ++i) sa[i] = s1[sa[i]];
        fill(sa + n1, sa + n, -1);
        for (int i = n1 - 1; i >= 0; --i) { int j = sa[i]; sa[i] = -1; sa[--bucket[s[j]]] = j; }
        induce_sa_l(s, n, sa, ls, bucket, bucketSize);
        induce_sa_s(s, n, sa, ls, bucket, bucketSize);
    }
    void getBuckets(vi& bucket, int bucketSize, vi::iterator s, int n, bool end) {
        bucket.assign(bucketSize + 1, 0);
        for (int i = 0; i < n; ++i) bucket[s[i]]++;
        for (int i = 0, sum = 0; i <= bucketSize; ++i) { sum += bucket[i]; bucket[i] = end ? sum : sum - bucket[i]; }
    }
    void induce_sa_l(vi::iterator s, int n, vi::iterator SA, vector<bool>& ls, vi& bucket, int bucketSize) {
        getBuckets(bucket, bucketSize, s, n, false);
        for (int i = 0; i < n; ++i) { int j = SA[i] - 1; if (j >= 0 && !ls[j]) SA[bucket[s[j]]++] = j; }
    }
    void induce_sa_s(vi::iterator s, int n, vi::iterator SA, vector<bool>& ls, vi& bucket, int bucketSize) {
        getBuckets(bucket, bucketSize, s, n, true);
        for (int i = n - 1; i >= 0; --i) { int j = SA[i] - 1; if (j >= 0 && ls[j]) SA[--bucket[s[j]]] = j; }
    }
    bool isLMS(vector<bool>& t, int i) { return i > 0 && t[i] && !t[i - 1]; }
};

int main() {
    int N;
    string S;
    cin >> N >> S;

    SuffixArrayByInducedSorting<128> sa;
    sa.build_sa(S);
    sa.build_lcp();

    int ans = 0;
    rep(i, N + 1) {
        int cl = N;
        for (int j = i - 1; j >= 0; --j) {
            cl = min(cl, sa.lcp[j]);

            if (sa.sa[j] < sa.sa[i]) {
                ans = max(ans, min(cl, sa.sa[i] - sa.sa[j]));
            }
            else {
                ans = max(ans, min(cl, sa.sa[j] - sa.sa[i]));
            }
        }
        cl = N;
        for (int j = i + 1; j < N; ++j) {
            cl = min(cl, sa.lcp[j - 1]);

            if (sa.sa[j] < sa.sa[i]) {
                ans = max(ans, min(cl, sa.sa[i] - sa.sa[j]));
            }
            else {
                ans = max(ans, min(cl, sa.sa[j] - sa.sa[i]));
            }
        }
    }

    write(ans);
}

Measured memory_bytes: 384