#include <bits/stdc++.h>
#define whlie while
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define rep(i,N) for(int i = 0; i < (N); i++)
#define repr(i,N) for(int i = (N) - 1; i >= 0; i--)
#define rep1(i,N) for(int i = 1; i <= (N) ; i++)
#define repr1(i,N) for(int i = (N) ; i > 0 ; i--)
#define each(x,v) for(auto& x : v)
#define all(v) (v).begin(),(v).end()
#define sz(v) ((int)(v).size())
#define vrep(v,it) for(auto it = v.begin(); it != v.end(); it++)
#define vrepr(v,it) for(auto it = v.rbegin(); it != v.rend(); it++)
#define ini(...) int __VA_ARGS__; in(__VA_ARGS__)
#define inl(...) ll __VA_ARGS__; in(__VA_ARGS__)
#define ins(...) string __VA_ARGS__; in(__VA_ARGS__)
using namespace std; void solve();
using ll = long long; using vl = vector<ll>;
using vi = vector<int>; using vvi = vector< vector<int> >;
constexpr int inf = 1001001001;
constexpr ll infLL = (1LL << 61) - 1;
struct IoSetupNya {IoSetupNya() { cin.tie(nullptr); ios::sync_with_stdio(false); cout << fixed << setprecision(15); cerr << fixed << setprecision(7);} } iosetupnya;
template<typename T, typename U> inline bool amin(T &x, U y) { return (y < x) ? (x = y, true) : false; }
template<typename T, typename U> inline bool amax(T &x, U y) { return (x < y) ? (x = y, true) : false; }
template<typename T, typename U> ostream& operator <<(ostream& os, const pair<T, U> &p) { os << p.first << " " << p.second; return os; }
template<typename T, typename U> istream& operator >>(istream& is, pair<T, U> &p) { is >> p.first >> p.second; return is; }
template<typename T> ostream& operator <<(ostream& os, const vector<T> &v) { int s = (int)v.size(); rep(i,s) os << (i ? " " : "") << v[i]; return os; }
template<typename T> istream& operator >>(istream& is, vector<T> &v) { for(auto &x : v) is >> x; return is; }
void in(){} template <typename T,class... U> void in(T &t,U &...u){ cin >> t; in(u...);}
void out(){cout << "\n";} template <typename T,class... U> void out(const T &t,const U &...u){ cout << t; if(sizeof...(u)) cout << " "; out(u...);}
template<typename T>void die(T x){out(x); exit(0);}
#ifdef NyaanDebug
  #include "NyaanDebug.h"
  #define trc(...) do { cerr << #__VA_ARGS__ << " = "; dbg_out(__VA_ARGS__);} while(0)
  #define trca(v,...) do { cerr << #v << " = "; array_out(v , __VA_ARGS__ );} while(0)
#else
  #define trc(...)
  #define trca(...)
  int main(){solve();}
#endif
 
using P = pair<int,int>; using vp = vector<P>;
constexpr int MOD = /**/ 1000000007; //*/ 998244353;
/////////////////


//verify https://judge.yosupo.jp/submission/240
struct SuffixArray{
  int _size;
  vector<int> sa;
  string &s;
  SuffixArray(string &str):_size(str.size()) , s(str) {
    // うしさんのO( N logN )の実装
    s.push_back(0);
    sa.resize(s.size());
    iota(begin(sa), end(sa), 0);
    sort(begin(sa), end(sa), [&](int a, int b) {
      return s[a] == s[b] ? a > b : s[a] < s[b];
    });
    vector< int > classes(s.size()), c(s.begin(), s.end()), cnt(s.size());
    for(int len = 1; len < (int)s.size(); len <<= 1) {
      for(int i = 0; i < (int)s.size(); i++) {
        if(i > 0 && c[sa[i - 1]] == c[sa[i]] && sa[i - 1] + len < (int)s.size() && c[sa[i - 1] + len / 2] == c[sa[i] + len / 2]) {
          classes[sa[i]] = classes[sa[i - 1]];
        } else {
          classes[sa[i]] = i;
        }
      }
      iota(begin(cnt), end(cnt), 0);
      copy(begin(sa), end(sa), begin(c));
      for(int i = 0; i < (int)s.size(); i++) {
        int s1 = c[i] - len;
        if(s1 >= 0) sa[cnt[classes[s1]]++] = s1;
      }
      classes.swap(c);
    }
    s.pop_back();
  }
  // デバッグ用に実装
  void output() {
    cout << "SA\tidx\tstr" << endl;
    for(int i = 0; i < size(); i++) {
      cout << i << ": \t" << sa[i] << " \t" ;
      if(sa[i] != _size) cout << s.substr(sa[i],_size - sa[i]) << endl;
      else cout << "$" << endl;
    }
    cout << endl;
  }
  // sa.size()と表せると便利なので実装
  int size() const{return _size + 1;}
  // sa[]と表せると便利なのでオーバーロードしておく
  int operator[](int k) const{return sa[k]; }
};

struct LCPArray {
  const SuffixArray &SA;
  vector<int> LCP, rank;
  LCPArray(const SuffixArray &sa) : SA(sa) {
    LCP.resize(SA.size()); rank.resize(SA.size());
    // 初期化　rankはsaの逆関数
    for(int i = 0; i < SA.size(); i++) {
      rank[SA[i]] = i;
    }
    LCP[0] = 0; 
    
    // 構築
    for(int i = 0, h = 0; i < SA.size() - 1 ; i++) {
      int j = SA[rank[i] - 1] ; h ? h-- : h;
      // ここで尺取り法に近い手法を使うことでO(N)でLCPの構築をしている
      while( (i > j ? i : j) + h < SA.size() - 1 && SA.s[i + h] == SA.s[j + h] && ++h );
      LCP[rank[i] - 1] = h;
    }
  }

  // デバッグ用に実装
  void output() {
    cout << "SA\tidx\tLCP\tstr" << endl;
     for(int i = 0 ; i < SA.size() ; i++){
      cout << i << "\t" << SA[i] <<" \t" << LCP[i] << "\t"; 
      if(SA[i] == SA.size() - 1) cout << "$";
      else cout << SA.s.substr(SA[i] , SA.size() - 1 - SA[i]);
      cout << endl;
    }
  }

};

// Sparse Table
template<typename T>
struct SparseTable{
  vector< vector< T > > table;
  vector< int > log_table;

  SparseTable(const vector< T > &v) {
    int b = 0;
    while((1 << b) <= (int)v.size()) ++b;
    table.assign(b, vector< T >(1 << b));
    for(int i = 0; i < (int)v.size(); i++) {
      table[0][i] = v[i];
    }
    for(int i = 1; i < b; i++) {
      for(int j = 0; j + (1 << i) <= (1 << b); j++) {
        table[i][j] = min(table[i - 1][j], table[i - 1][j + (1 << (i - 1))]);
      }
    }
    log_table.resize(v.size() + 1);
    for(int i = 2; i < (int)log_table.size(); i++) {
      log_table[i] = log_table[i >> 1] + 1;
    }
  }

  // 区間 [l , r) の最小値を返す
  inline T query(int l, int r) {
    int b = log_table[r - l];
    return min(table[b][l], table[b][r - (1 << b)]);
  }
};

// 文字列検索 検索 O(M + logN) メモリO(N logN)
// verify
// https://onlinejudge.u-aizu.ac.jp/status/users/NyaanNyaan/submissions/1/ALDS1_14_D/judge/3874273/C++14
// https://atcoder.jp/contests/abc135/submissions/7574225
// https://judge.yosupo.jp/submission/241
// https://atcoder.jp/contests/abc141/submissions/7577295
struct StringSearch{
  string &s;
  const SuffixArray &sa;
  const LCPArray &lcp;
  SparseTable<int> sparse;
  StringSearch(LCPArray &lcp)
    : s(lcp.SA.s) , sa(lcp.SA) , lcp(lcp) , sparse(lcp.LCP){ }

 // 文字列sの[i , N)と[j , N)の共通接頭辞の長さを求める
  int ArbitaryLCP(int i , int j){
    if(i == j) return (int)(s.size()) - i;
    return sparse.query(
      min(lcp.rank[i] , lcp.rank[j]) , 
      max(lcp.rank[i] , lcp.rank[j])  
    );
  }

  pair<int,int> comp(const string &t  , int len , int si , int ti = 0){
    int sn = (int)s.size() , tn = (int) t.size();
    si += len , ti += len;
    while(si < sn && ti < tn){
      if(s[si] != t[ti]) return make_pair( s[si]<t[ti] , ti);
      si++ , ti++;
    }
    return make_pair( (si>=sn && ti<tn) , ti);
  }  

  // 全ての出現範囲をSA上の[left , right]の範囲で返す
  // 存在しない場合は-1を返す
  pair<int,int> find(string &t){
    // 条件を満たす[left , right]を見つける
    // sa[0]は空文字列なので left = 1 とする
    // lenは既に一致している文字列の長さ
    int left = 1 , right = sa.size() - 1, len = 0;
    while(1){
      int med = (left + right) / 2;
      pair<int,int> ret = comp(t , len , sa[med]);
      if(len < ret.se){
        len = ret.se;
        {
          int ng = left - 1, ok = med;
          whlie(ng + 1 < ok){
            int cur = (ng + ok) / 2;
            if(sparse.query(cur , med) >= len) ok = cur;
            else ng = cur;
          }
          left = ok;
        }
        {
          int ok = med , ng = right + 1;
          whlie(ok + 1 < ng){
            int cur = (ng + ok) / 2;
            if(sparse.query(med, cur) >= len) ok = cur;
            else ng = cur;
          }
          right = ok;
        }      
        if(len == (int)t.size()) return make_pair(left , right);
      }
      else{
        if(ret.fi == 1) left = med;
        else right = med;
      }
      if(left + 1 >= right){
        if(comp(t , len , sa[left + right - med]).se == (int)t.size()){
          return make_pair(left + right - med , left + right - med);
        }
        else return make_pair(-1 , -1);
      }
    }
  }
};

// Suffix Arrayの使い方(メモリを食うので必要なものだけ使う)
// 参照があるのでstringを削除などしないこと
/*
  SuffixArray sa(S);
  LCPArray lcp(sa);
  StringSearch search(lcp);
*/

void solve(){
  ini(N);
  ins(S);
  SuffixArray sa(S);
  LCPArray lcp(sa);
  StringSearch search(lcp);
  int ans = 0;
  rep(i , N) rep(j , i){
    amax(ans , min(i - j , search.ArbitaryLCP(i , j)));
  }
  out(ans);
}

Measured memory_bytes: 768