#include <stdio.h>
#include <set>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <list>
#include <queue>
#include <stack>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <vector>
#include <string>
#include <bitset>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <utility>
#include <numeric>
#include <complex>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <cassert>
#include <iostream>
#include <iterator>
#include <algorithm>
#include <functional>
using namespace std;
typedef long long ll;
const double EPS = 1e-9;
typedef vector<int> vint;
typedef vector<vector<int>> v2int;
typedef vector<ll> vll;
typedef vector<vector<ll>> v2ll;
typedef list<int> liint;
typedef pair<int, int> pint;
const int INF = int(2e9);
const ll LINF = ll(2e9) * ll(2e9);
#define rep(i, n) REP(i, 0, n)
#define ALL(v) v.begin(), v.end()
#define MSG(a) cout << #a << " " << a << endl;
#define REP(i, x, n) for(int i = x; i < n; i++)
template<class T, class C> void chmax(T& a, C b) { a > b ? : a = b; }
template<class T, class C> void chmin(T& a, C b) { a < b ? : a = b; }


class RollingHash {
private:
    ll base1;
    ll base2;
    ll mod1;
    ll mod2;
    vector<ll> hash1;
    vector<ll> hash2;
    vector<ll> pow1;
    vector<ll> pow2;
public:
    RollingHash() : base1(1009), base2(1007), mod1(1000000007), mod2(1000000009) {}
    //RollingHash() : base1(1009),  mod1(1000000007) {}
    RollingHash(const string &s) : base1(1009), base2(1007), mod1(1000000007), mod2(1000000009),
                                    hash1(s.size() + 1, 0), pow1(s.size() + 1, 1),
                                    hash2(s.size() + 1, 0), pow2(s.size() + 1, 1) {
    
        for (int i = 0; i<s.size(); i++) {
            hash1[i + 1] = (hash1[i] + s[i]) * base1 % mod1;
            hash2[i + 1] = (hash2[i] + s[i]) * base2 % mod2;
            pow1[i + 1] = pow1[i] * base1 % mod1;
            pow2[i + 1] = pow2[i] * base2 % mod2;
        }
    }
    //void init(const string &s) {
    //    int n = s.size();

    //    hash1.assign(n + 1, 0);
    //    //hash2.assign(n + 1, 0);
    //    pow1.assign(n + 1, 1);
    //    //pow2.assign(n + 1, 1);

    //    for (int i = 0; i<n; i++) {
    //        hash1[i + 1] = (hash1[i] + s[i]) * base1 % mod1;
    //        //hash2[i + 1] = (hash2[i] + s[i]) * base2 % mod2;
    //        pow1[i + 1] = pow1[i] * base1 % mod1;
    //        //pow2[i + 1] = pow2[i] * base2 % mod2;
    //    }
    //}

    pair<ll, ll> get(int l, int r) {
        ll t1 = ((hash1[r] - hash1[l] * pow1[r - l]) % mod1 + mod1) % mod1;
        ll t2 = ((hash2[r] - hash2[l] * pow2[r - l]) % mod2 + mod2) % mod2;
        //ll t2 = 1;
        //return t1;
        return make_pair(t1, t2);
    }
    ll get1(int l, int r) {
        ll t1 = ((hash1[r] - hash1[l] * pow1[r - l]) % mod1 + mod1) % mod1;
        //ll t2 = 1;
        return t1;
    }
    ll get2(int l, int r) {
        ll t2 = ((hash2[r] - hash2[l] * pow2[r - l]) % mod2 + mod2) % mod2;
        //ll t2 = 1;
        return t2;
    }
    //pair<ll, ll> concat(pair<ll, ll>& h1, pair<ll, ll>& h2, int h2_len) {
    //    return make_pair((h1.first*pow1[h2_len] + h2.first) % mod1, (h1.second*pow2[h2_len] + h2.second) % mod2);
    //}

};

//bool is_substr(const char* S, int S_size, const char* T, int T_size) {
//    int t = 0;
//    //S_size = strlen(S);
//    //T_size = strlen(T);
//    rep(i, S_size) {
//        if (S[i] == T[t]) {
//            t++;
//            if (t >= T_size) {
//                return true;
//            }
//        }
//        else {
//            if (S[i] == T[0]) {
//                t = 1;
//            }
//            else {
//                t = 0;
//            }
//        }
//    }
//    return false;
//}
//
//bool is_substr(string& S, string& T) {
//    //int t = 0;
//    if (T.size() > S.size()) return false;
//    int i = 0, t = 0;
//    while (1) {
//        if (S[i] == T[t]) {
//            t++;
//            i++;
//            if (t >= T.size()) {
//                return true;
//            }
//        }
//        else if (t == 0) {
//            i++;
//        }
//        else {
//            t = 0;
//        }
//        if (i >= S.size()) break;
//    }
//    //rep(i, S.size()) {
//    //    if (S[i] == T[t]) {
//    //        t++;
//    //        if (t >= T.size()) {
//    //            return true;
//    //        }
//    //    }
//    //    else {
//    //        if (S[i] == T[0]) {
//    //            t = 1;
//    //        }
//    //        else {
//    //            t = 0;
//    //        }
//    //        if (t >= T.size()) {
//    //            return true;
//    //        }
//    //    }
//    //}
//    return false;
//}

//bool is_exist(string& S, int N, int len, RollingHash& RH) {
//    if (len == 0) return true;
//    for (int s = 0; s <= N - len; s++) {
//        //string T = S.substr(s, len);
//        auto Thash = RH.get(s, s + len);
//        for (int i = s + len; i <= N - len; i++) {
//            auto Shash = RH.get(i, i + len);
//            if (Thash == Shash) return true;
//        }
//        //string Ssub = S.substr(s + len);
//        //if(is_substr(Ssub, T) == true)
//        //if (is_substr((S.c_str() + len + s), N - len - s, T.c_str(), len) == true)
//            //return true;
//    }
//    return false;
//}

unordered_map<int, int> not_exsits;
bool is_exist(RollingHash& RH, int N, int len) {
    if (len == 0) return true;
    
    for (int s = 0; s <= N - len; s++) {
        if (not_exsits[s] != 0 && not_exsits[s] < len)
            continue;
        auto Thash1 = RH.get1(s, s + len);
        auto Thash2 = RH.get2(s, s + len);
        for (int i = s + len; i <= N - len; i++) {
            auto Shash1 = RH.get1(i, i + len);
            if (Thash1 == Shash1) {
                auto Shash2 = RH.get2(i, i + len);
                if (Thash2 == Shash2)
                    return true;
            }
        }
        not_exsits[s] = len;
    }
    return false;
}
int bin_search(RollingHash& RH, int N) {
    int lb = -1, ub = N;
    while (ub - lb > 1) {
        int mid = (ub + lb) / 2;
        if (is_exist(RH, N, mid) == true) {
            lb = mid;
        }
        else {
            ub = mid;
        }
    }
    //if (is_exist(S, N, ub) == true) return ub;
    return lb;

}

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

    RollingHash RH(S);

    //RH.init(S);

    cout << bin_search(RH, N) << endl;
    return 0;
}

Measured memory_bytes: 640