#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr long long INF = (1LL<<60);
#define rep(i,n) for(ll i=0;i<(n);++i)

class Sparse_Table{
private:
  ll n;
  vector<vector<ll>> st;
  vector<ll> lookup;
public:
  Sparse_Table(){}
  Sparse_Table(const vector<ll>& vec){
    n=vec.size();
    ll b=0;
    while((1<<b)<n)++b;
    st.resize(b,vector<ll>(1<<b));
    for(ll i=0;i<n;++i){
      st[0][i]=vec[i];
    }
    for(ll i=1;i<b;++i){
      for(ll j=0;j+(1<<i)<=(1<<b);++j){
        st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
      }
    }
    lookup.resize(n+1);
    for(ll i=2;i<=n;++i){
      lookup[i]=lookup[i>>1]+1;
    }
  }
  auto rmq(ll x,ll y){
    ll b=lookup[y-x];
    return min(st[b][x],st[b][y-(1<<b)]);
  }
};

// usi tree
class Segment_Tree {
private:
  ll n=1;
  ll init=INF;
  std::vector<ll> node;
  ll f(ll x,ll y){
    return min(x,y);
  }
public:
  Segment_Tree(){}
  Segment_Tree(vector<ll> v){
    ll sz = ((ll)v.size());    
    while(n<sz)n<<=1;
    node.resize(n*2-1,init);
    for(ll i=0;i<sz;++i)node[i+n-1]=v[i];
    for(ll i=n-2;i>=0;--i)node[i]=f(node[i*2+1],node[i*2+2]);
  }
  void build(vector<ll> v){
    ll sz = ((ll)v.size());    
    while(n<sz)n<<=1;
    node.resize(n*2-1,init);
    for(ll i=0;i<sz;++i)node[i+n-1]=v[i];
    for(ll i=n-2;i>=0;--i)node[i]=f(node[i*2+1],node[i*2+2]);
  }
  void update(ll idx,ll x){
    idx+=n-1;
    node[idx]=x;
    while(idx){
      idx--;idx>>=1;
      node[idx]=f(node[idx*2+1],node[idx*2+2]);
    }
  }
  void add(ll idx,ll x){
    idx+=n-1;
    node[idx]+=x;
    while(idx){
      idx--;idx>>=1;
      node[idx]=f(node[idx*2+1],node[idx*2+2]);
    }
  }
  ll query(ll a,ll b,ll k=0,ll l=0,ll r=-1){
    if(r<0)r=n;
    if(r<=a || b<=l)return init;
    if(a<=l && r<=b)return node[k];
    else{
      ll vl = query(a,b,2*k+1,l,(l+r)/2);
      ll vr = query(a,b,2*k+2,(l+r)/2,r);
      return f(vl,vr);
    }
  }
};

class SuffixArray{
private:
  ll n;
  string s;
  vector<ll> SA;

public:
  SuffixArray(const string& str):s(str){
    n=s.size();
    SA.resize(n);
    iota(SA.begin(),SA.end(),0);
    auto firstcmp = [&](auto a,auto b){
      return s[a]==s[b] ? a>b : s[a]<s[b] ;
    };
    sort(SA.begin(),SA.end(),firstcmp);
    vector<ll> classes(n),cnt(n),c(s.begin(),s.end());
    for(ll k=1;k<n;k<<=1){
      for(ll i=0;i<n;++i){
        if( i>0 && c[SA[i-1]]==c[SA[i]] && SA[i-1]+k<n && c[SA[i-1]+k/2]==c[SA[i]+k/2] ){
          classes[SA[i]] = classes[SA[i-1]];
        }
        else classes[SA[i]] = i;
      }
      iota(cnt.begin(),cnt.end(),0);
      copy(SA.begin(),SA.end(),c.begin());
      for(ll i=0;i<n;++i){
        ll sl = c[i]-k;
        if(sl>=0) SA[cnt[classes[sl]]++] = sl;
      }
      classes.swap(c);
    }
  }
  inline ll operator[](ll idx) const {
    return SA[idx];
  }
  inline size_t size() const {
    return s.size();
  }
  inline char str(ll idx) const {
    return s[idx];
  }
  inline string substr(ll idx) const {
    return s.substr(idx);
  }
  auto SA_array() const {
    return SA;
  }

  bool lt_substr(const string& t,ll si=0,ll ti=0){
    ll sn=n,tn=t.size();
    while( si<sn && ti<tn ){
      if(s[si]<t[ti])return true;
      if(s[si]>t[ti])return false;
      ++si;++ti;
    }
    return si>=sn && ti<tn;
  }
  ll lower_bound(const string& t){
    ll low=-1,high=n;
    while(high-low>1){
      ll mid=(high+low)>>1;
      if(lt_substr(t,SA[mid]))low=mid;
      else high=mid;
    }
    return high;
  }
  auto lower_upper_bound(string& t){
    ll idx=lower_bound(t);
    ll low=idx-1,high=n;
    t.back()++;
    while(high-low>1){
      ll mid=(high+low)>>1;
      if(lt_substr(t,SA[mid]))low=mid;
      else high=mid;
    }
    t.back()--;
    return make_pair(idx,high);
  }

  void dump(){
    for(ll i=0;i<n;++i){
      cout<<i<<":"<<SA[i]<<" "<<s.substr(SA[i])<<endl;
    }
  }
};

class LongestCommonPrefixArray{
private:
  ll n;
  const SuffixArray &SA;
  vector<ll> LCP,rank;
public:
  LongestCommonPrefixArray(const SuffixArray& SA):SA(SA){
    n=SA.size();
    LCP.resize(n);
    rank.resize(n);
    for(ll i=0;i<n;++i){
      rank[SA[i]] = i;
    }
    for(ll i=0,h=0;i<n;++i){
      if(rank[i]+1<n){
        for(ll j=SA[rank[i]+1]; max(i,j)+h<n && SA.str(i+h)==SA.str(j+h); ++h );
        LCP[rank[i]+1] = h;
        if(h>0) --h;
      }
    }
  }
  inline ll operator[](ll idx) const {
    return LCP[idx];
  }
  inline size_t size() const {
    return LCP.size();
  }
  auto LCP_array() const {
    return LCP;
  }
  auto Rank_array() const {
    return rank;
  }

  Segment_Tree seg;
  void RMQ(){
    seg=Segment_Tree(LCP);
  }
  auto get(ll a,ll b){
    ll x=min(rank[a],rank[b])+1;
    ll y=max(rank[a],rank[b])+1;
    ll v=seg.query(x,y);
    return v;
  }

  Sparse_Table sp;
  void sparse_table(){
    sp=Sparse_Table(LCP);
  }
  auto rmq(ll a,ll b){
    ll x=min(rank[a],rank[b])+1;
    ll y=max(rank[a],rank[b])+1;
    ll v=sp.rmq(x,y);
    return v;
  }

  void dump(){
    for(ll i=0;i<n;++i){
      cout<<i<<": "<<SA[i]<<" "<<LCP[i]<<" "<<SA.substr(SA[i])<<endl;
    }
  }
};

main(){

  ll n;
  string s;
  cin>>n>>s;

  SuffixArray sa(s);
  LongestCommonPrefixArray lcp(sa);
  lcp.sparse_table();
  ll ans=0;
  rep(i,n){
    for(ll j=i+1;j<n;++j){
      ans=max(ans,min(lcp.rmq(i,j),j-i));
    }
  }
  cout<<(ans)<<endl;

}

Measured memory_bytes: 1280