#include <bits/stdc++.h>

#define rep(i, n) for(int i = 0; i < n; i++)

using namespace std;
using ll = long long;
int mod = 1000000007;
int Rank[1000001];

#include<bits/stdc++.h>

using namespace std;

int INF = INT_MAX;

struct SegmentTree{
private:
    int n; //セグ木のサイズ(=ノード数)
    vector<int> node;

public:
    //コンストラクタ: 元配列vを基にセグメント木を作成
    SegmentTree(){}
    SegmentTree(vector<int> v){
        int sz = v.size();
        int height = ceil(log2(sz)); // sz = 4 -> height = 2, sz = 5 -> height = 3
        n = pow(2, height); //セグ木における葉の数
        node.resize(2*n-1, INF); 

        //まずは最下段に値を入れる
        for(int i = 0; i < sz; i++) node[i+n-1] = v[i];
        //次に、根に登りつつ値を入れる
        for(int i = n-2; i >= 0; i--) node[i] = min(node[(i << 1) + 1], node[(i << 1) + 2]);
    }
    //値の取得クエリ [l, r)の最小値を取得
    int getmin(int a, int b, int k=0, int l=0, int r=-1){
        //最初の呼び出し時の対象区間は[0, n)
        if(r < 0) r = n;

        //[a, b)と[l, r)が交わらない -> INFを返す
        if(r <= a || b <= l) return INF;

        //[a, b)は[l, r)を完全に含む -> 現在のノードの値を返す
        if(a <= l && r <= b) return node[k];

        //[a, b)は[l, r)を一部含む -> 子について探索を行う
        int vl = getmin(a, b, (k << 1) + 1, l, (l+r)/2);
        int vr = getmin(a, b, (k << 1) + 2, (l+r)/2, r);
        return min(vl, vr);
    }

    //値の1点更新クエリ
    void update(int idx, int val){
        node[idx + (n-1)] = val;
        idx = idx + (n-1);
        while(idx > 0){
            idx = (idx % 2 == 0)? idx/2 - 1 : idx/2;
            node[idx] = min(node[(idx << 1)+1], node[(idx << 1)+2]);
        }
    }

    void show(){
        for(int i = 0; i < n; i++){
            cout << node[i + (n-1)] << " ";
        }
        cout << endl;
    }
};

vector<int> sort_cyclic_shifts(string const &s){
    int n = s.size();
    const int alphabet = 256;
    vector<int> p(n), c(n), cnt(max(alphabet, n), 0);

    //k = 0でのソート
    rep(i, n) cnt[s[i]]++; //各文字の出現回数をカウント
    for(int i = 1; i < alphabet; i++) cnt[i] += cnt[i-1];
    rep(i, n) p[--cnt[s[i]]] = i;

    c[p[0]] = 0;
    int classes = 1;
    for(int i = 1; i < n; i++){
        if(s[p[i]] != s[p[i-1]]) classes++;
        c[p[i]] = classes-1;
    }

    //k = 1からk = logn + 1のソート
    vector<int> pn(n), cn(n);

    for(int k = 0; (1 << k) < n; k++){
        for(int i = 0; i < n; i++){
            pn[i] = p[i] - (1 << k);
            if(pn[i] < 0)
                pn[i] += n;
        }
        fill(cnt.begin(), cnt.begin() + classes, 0);
        for(int i = 0; i < n; i++) cnt[c[pn[i]]]++;
        for(int i = 1; i < classes; i++) cnt[i] += cnt[i-1];
        for(int i = n-1; i >= 0; i--) p[--cnt[c[pn[i]]]] = pn[i];

        cn[p[0]] = 0;
        classes = 1;
        for(int i = 1; i < n; i++){
            pair<int, int> cur = {c[p[i]], c[(p[i] + (1 << k) % n)]};
            pair<int, int> prev = {c[p[i-1]], c[(p[i-1] + (1 << k)) % n]};
            if(cur != prev) ++classes;
            cn[p[i]] = classes - 1;
        }
        c.swap(cn);
    }
    return p;
}

vector<int> suffix_array_construction(string s){
    s += "$";
    vector<int> sorted_shifts = sort_cyclic_shifts(s);
    sorted_shifts.erase(sorted_shifts.begin());
    return sorted_shifts;
}

void construct_lcp(string s, vector<int>& sf, vector<int>& lcp){
    int n = s.length();
    for(int i = 0; i < n; i++) Rank[sf[i]] = i;

    int h = 0;
    for(int i = 0; i < n; i++){
        if(Rank[i] == 0) continue;
        int j = sf[Rank[i] - 1];

        if(h > 0) h--;
        for(; j+h < n && i+h < n; h++){
            if(s[j+h] != s[i+h]) break;
        }
        lcp[Rank[i]-1] = h;
    }
}

int main(void){
    int N; cin >> N;
    string s; cin >> s;
    vector<int> sf = suffix_array_construction(s);
    vector<int> lcp(sf.size(), 0);
    construct_lcp(s, sf, lcp);
    SegmentTree st(lcp);
    //lcp[i]: 辞書順でi番目に来る接尾辞(sf[i]から始まる)と
    //      : 辞書順でi+1番目に来る接尾辞(sf[i+1]から始まる)
    //      : の先頭から共通する文字数
    //i文字目からの文字列とj文字目からの文字列の最長共通文字数
    //i文字目からの文字列のlcp上の位置は?
    //rank[sf[i]] = i 
    int ans = 0;
    for(int i = 0; i < N; i++){
        int posi = Rank[i];
        for(int j = i+1; j < N; j++){
            int posj = Rank[j];
            int a = min(posi, posj);
            int b = max(posi, posj);
            ans = max(ans, min(j-i, st.getmin(a, b)));
        }
    }
    cout << ans << endl;
    return 0;
}

Measured memory_bytes: 512