// Slower C++ solution:
#include <bits/stdc++.h>

#define LLI long long int

#define FOR(v, a, b) for(LLI v = (a); v < (b); ++v)

#define FORE(v, a, b) for(LLI v = (a); v <= (b); ++v)

#define REP(v, n) FOR(v, 0, n)

#define REPE(v, n) FORE(v, 0, n)

#define REV(v, a, b) for(LLI v = (a); v >= (b); --v)

#define ALL(x) (x).begin(), (x).end()

#define RALL(x) (x).rbegin(), (x).rend()

#define ITR(it, c) for(auto it = (c).begin(); it != (c).end(); ++it)

#define RITR(it, c) for(auto it = (c).rbegin(); it != (c).rend(); ++it)

#define EXIST(c,x) ((c).find(x) != (c).end())

#define fst first

#define snd second

#define popcount __builtin_popcount

#define UNIQ(v) (v).erase(unique(ALL(v)), (v).end())

#define bit(i) (1LL<<(i))



#ifdef DEBUG

#include <misc/C++/Debug.cpp>

#else

#define dump(...) ((void)0)

#endif



#define gcd __gcd



using namespace std;

template <class T> constexpr T lcm(T m, T n){return m/gcd(m,n)*n;}



template <typename I> void join(ostream &ost, I s, I t, string d=" "){for(auto i=s; i!=t; ++i){if(i!=s)ost<<d; ost<<*i;}ost<<endl;}

template <typename T> istream& operator>>(istream &is, vector<T> &v){for(auto &a : v) is >> a; return is;}



template <typename T, typename U> bool chmin(T &a, const U &b){return (a>b ? a=b, true : false);}

template <typename T, typename U> bool chmax(T &a, const U &b){return (a<b ? a=b, true : false);}

template <typename T, size_t N, typename U> void fill_array(T (&a)[N], const U &v){fill((U*)a, (U*)(a+N), v);}



struct Init{

  Init(){

    cin.tie(0);

    ios::sync_with_stdio(false);

  }

}init;



template <typename T, LLI BASE, LLI MOD> class RollingHash{

private:

  static vector<LLI> pow;



public:

  static void init(int N){

    pow.assign(N+1,0);

    pow[0] = 1;

    FORE(i,1,N){

      pow[i] = pow[i-1]*BASE % MOD;

    }

  }



  static vector<LLI> generate_hash(const T &s){

    vector<LLI> ret(s.size()+1);



    FORE(i,1,(int)s.size()){

      ret[i] = (ret[i-1]*BASE+s[i-1]) % MOD;

    }

    

    return ret;

  }



  static LLI hash(const vector<LLI> &shash, int i, int j){ // [i, j)

    return (shash[j] - shash[i] * pow[j-i] + MOD*MOD) % MOD;

  }



  static LLI hash(const T &p){

    LLI phash = 0;

    REP(i,(int)p.size()) phash = (phash*BASE+p[i]) % MOD;

    return phash;

  }



  

  static vector<int> find(const vector<LLI> &shash, const T &p){

    vector<int> ret;

    int pl = p.size();

    LLI phash = hash(p);

    REP(i,(int)shash.size()-pl) if(hash(shash,i,i+pl) == phash) ret.push_back(i);

    return ret;

  }

};



template <typename T, LLI BASE, LLI MOD> vector<LLI> RollingHash<T,BASE,MOD>::pow;



using R1 = RollingHash<string,31,1000000009>;

using R2 = RollingHash<string,41,998244353>;



int main(){

  R1::init(10000);

  R2::init(10000);

  

  int N;

  string s;

  while(cin >> N >> s){



    auto hs1 = R1::generate_hash(s);

    auto hs2 = R2::generate_hash(s);

    

    int ans = 0;



    auto check =

      [&](int L){

        set<pair<LLI,LLI>> store;

        queue<pair<LLI,LLI>> q;



        REP(i,N-L+1){

          auto h1 = R1::hash(hs1,i,i+L);

          auto h2 = R2::hash(hs2,i,i+L);

        

          pair<LLI,LLI> H(h1,h2);



          if(EXIST(store,H)){

            return true;

          }



          if((int)q.size() == L-1){

            q.push(H);

            auto t = q.front(); q.pop();

            store.insert(t);

          }else{

            q.push(H);

          }

        }



        return false;

      };



    int lb = 0, ub = N, mid;

    while(abs(lb-ub) > 1){

      mid = (lb+ub)/2;



      if(check(mid)){

        lb = mid;

      }else{

        ub = mid;

      }

    }



    ans = lb;



    cout << ans << endl;

  }





  return 0;

}


// Faster C++ solution for the same problem (measured speedup: 1.2386737420762397x):
#include <bits/stdc++.h>

#define LLI long long int

#define FOR(v, a, b) for(LLI v = (a); v < (b); ++v)

#define FORE(v, a, b) for(LLI v = (a); v <= (b); ++v)

#define REP(v, n) FOR(v, 0, n)

#define REPE(v, n) FORE(v, 0, n)

#define REV(v, a, b) for(LLI v = (a); v >= (b); --v)

#define ALL(x) (x).begin(), (x).end()

#define RALL(x) (x).rbegin(), (x).rend()

#define ITR(it, c) for(auto it = (c).begin(); it != (c).end(); ++it)

#define RITR(it, c) for(auto it = (c).rbegin(); it != (c).rend(); ++it)

#define EXIST(c,x) ((c).find(x) != (c).end())

#define fst first

#define snd second

#define popcount __builtin_popcount

#define UNIQ(v) (v).erase(unique(ALL(v)), (v).end())

#define bit(i) (1LL<<(i))



#ifdef DEBUG

#include <misc/C++/Debug.cpp>

#else

#define dump(...) ((void)0)

#endif



#define gcd __gcd



using namespace std;

template <class T> constexpr T lcm(T m, T n){return m/gcd(m,n)*n;}



template <typename I> void join(ostream &ost, I s, I t, string d=" "){for(auto i=s; i!=t; ++i){if(i!=s)ost<<d; ost<<*i;}ost<<endl;}

template <typename T> istream& operator>>(istream &is, vector<T> &v){for(auto &a : v) is >> a; return is;}



template <typename T, typename U> bool chmin(T &a, const U &b){return (a>b ? a=b, true : false);}

template <typename T, typename U> bool chmax(T &a, const U &b){return (a<b ? a=b, true : false);}

template <typename T, size_t N, typename U> void fill_array(T (&a)[N], const U &v){fill((U*)a, (U*)(a+N), v);}



struct Init{

  Init(){

    cin.tie(0);

    ios::sync_with_stdio(false);

  }

}init;



template <typename T> class SparseTableMin{

  vector<vector<T>> a;

  vector<int> log_table;

public:

  SparseTableMin(){}

  SparseTableMin(const vector<T> &v){

    int n = v.size();

    int logn = 0;

    while((1<<logn) <= n) ++logn;

    

    a = vector<vector<T>>(n, vector<T>(logn));

    REP(i,n) a[i][0] = v[i];

    FOR(j,1,logn) REP(i,n) a[i][j] = min(a[i][j-1], a[min<int>(n-1, i+(1<<(j-1)))][j-1]);



    log_table = vector<int>(n+1);

    FOR(i,2,n+1) log_table[i] = log_table[i>>1] + 1;

  }

  

  T get(int s, int t) const { // [s,t)

    int k = log_table[t-s];

    return min(a[s][k], a[t-(1<<k)][k]);

  }

};





template <typename T> class SuffixArray{

public:

  const T str;

  int n;

  vector<int> sa, rank, lcp;



  SparseTableMin<int> sp_lcp;

  

  SuffixArray(const T &s): str(s), n(s.size()), sa(n), rank(n), lcp(n-1){

    vector<int> temp(n);

    REP(i,n) temp[i] = s[i];



    for(int l=1; l<n; l*=2){

      map<pair<int,int>, int> m2;

      REP(i,n) m2[make_pair(temp[i], i+l>=n?-1:temp[i+l])] = 0;

      int i=0;

      ITR(it,m2) it->second = i++;

      REP(i,n) temp[i] = m2[make_pair(temp[i], i+l>=n?-1:temp[i+l])];

    }

    

    REP(i,n) sa[temp[i]] = i;



    REP(i,n) rank[sa[i]] = i;



    /*

      calculate LCP array. O(n)

     */

    int h = 0;

    REP(i,n){

      if(rank[i] == 0) continue;

      int j = sa[rank[i]-1];



      if(h) --h;

      while(j+h < n and i+h < n){

        if(s[j+h] != s[i+h]) break;

        ++h;

      }



      lcp[rank[i]-1] = h;

    }



    sp_lcp = SparseTableMin<int>(lcp);

  }



  int get_lcp(int i, int j) const {

    if(i == j) return n - i;

    

    i = rank[i];

    j = rank[j];



    if(i > j) swap(i,j);



    return sp_lcp.get(i,j);

  }



  int operator[](int i){return sa[i];}



  bool starts_with(const T &s, int k){

    if(s.size() <= str.size()-k){

      REP(i,(int)s.size()){

        if(s[i] != str[k+i]) return false;

      }

      return true;

    }

    return false;

  }



  int lower_bound(const T &s){

    auto check =

      [&](int x){

        REP(i,(int)s.size()){

          if(sa[x]+i >= (int)str.size()) return false;

          if(s[i] < str[sa[x]+i]) return true;

          if(s[i] > str[sa[x]+i]) return false;

        }

        return true;

      };



    int lb=-1, ub=sa.size(), mid;

    while(abs(lb-ub)>1){

      mid = (lb+ub)/2;



      if(check(mid)){

        ub = mid;

      }else{

        lb = mid;

      }

    }

    

    return ub;

  }



  int upper_bound(const T &s){

    T t(s);



    t.back()++;

    int ret = lower_bound(t);

    t.back()--;

    return ret;

  }

};



int main(){

  int N;

  string s;

  while(cin >> N >> s){

    int ans = 0;



    dump(s);



    SuffixArray<string> sa(s);



    dump(sa.sa, sa.rank, sa.lcp);



    REP(i,N){

      dump(s.substr(sa[i]));

    }

    

    REP(i,N){

      REP(j,i){

        int t = sa.get_lcp(i,j);

        dump(make_tuple(i,j,t));

        if(j+t <= i) chmax(ans, t);

      }

    }



    cout << ans << endl;

  }



  return 0;

}