#include <iostream>
#include <string>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
#include <queue>
#include <stack>
#include <functional>
#include <bitset>
#include <assert.h>
using namespace std;
typedef long long ll;
typedef vector<ll> vl;
typedef vector<vl> vvl;
typedef vector<char> vc;
typedef vector<string> vs;
typedef vector<bool> vb;
typedef vector<double> vd;
typedef pair<ll,ll> P;
typedef vector<P> vpl;
typedef tuple<ll,ll,ll> tapu;
#define rep(i,n) for(ll i=0; i<(n); i++)
#define REP(i,a,b) for(int i=(a); i<(b); i++)
#define all(x) (x).begin(), (x).end()
#define rall(x) (x).rbegin(), (x).rend()
const int inf = 1<<30;
const ll linf = 1LL<<62;
const int MAX = 510000;
ll dy[8] = {0,1,0,-1,1,-1,1,-1};
ll dx[8] = {1,0,-1,0,1,-1,-1,1};
const double pi = acos(-1);
const double eps = 1e-7;
template<typename T1,typename T2> inline bool chmin(T1 &a,T2 b){
	if(a>b){
		a = b; return true;
	}
	else return false;
}
template<typename T1,typename T2> inline bool chmax(T1 &a,T2 b){
	if(a<b){
		a = b; return true;
	}
	else return false;
}
template<typename T1,typename T2> inline void print2(T1 a, T2 b){cout << a << " " << b << "\n";}
template<typename T1,typename T2,typename T3> inline void print3(T1 a, T2 b, T3 c){
	cout << a << " " << b << " " << c << "\n";
}
const int mod = 1e9 + 7;
//const int mod = 998244353;


struct SuffixArray{
	vector<int> ran;
	vector<int> tmp;
	vector<int> sa;
	vector<int> rsa;
	vector<int> lcp;
	int k=0, n=0;
	string str;

	SuffixArray(string s){
		str = s;
		n = s.size();
		ran.resize(n+1,0);
		tmp.resize(n+1,0);
		sa.resize(n+1,0);
		rsa.resize(n+1,0);
		lcp.resize(n+1,0);
		build();
		calcLCP();
	}

	struct CompareSA {
        int n, k;
        const vector<int> &rank;
        CompareSA(int n, int k, const vector<int> &rank_sa) : n(n), k(k), rank(rank_sa) {}
        bool operator()(int i, int j) {
            if (rank[i] != rank[j]) return (rank[i] < rank[j]);
            else {
                int rank_ik = (i + k <= n ? rank[i + k] : -1);
                int rank_jk = (j + k <= n ? rank[j + k] : -1);
                return (rank_ik < rank_jk);
            }
        }
    };

	void build(){
		for(int i=0; i<=n; i++){
			sa[i] = i;
			ran[i] = i < n ? str[i] : -1;
		}
		for(k=1; k<=n; k*=2){
			CompareSA csa(n, k, ran);
			sort(sa.begin(),sa.end(),csa);
			tmp[sa[0]] = 0;
			for(int i=1; i<=n; i++){
				tmp[sa[i]] = tmp[sa[i-1]] + (csa(sa[i-1], sa[i]) ? 1 : 0);
			}
			for(int i=0; i<=n; i++){
				ran[i] = tmp[i];
			}
		}
	}

	bool contain(string t){
		int a = 0, b = str.size();
		while(b - a > 1){
			int c = (a+b) / 2;
			if(str.compare(sa[c], t.size(), t) < 0) a = c;
			else b = c;
		}
		return str.compare(sa[b], t.size(), t) == 0;
	}

	void calcLCP(){
		for(int i=0; i<=n; i++) rsa[sa[i]] = i;
		lcp[0] = 0;
		int cur = 0;
		for(int i=0; i<=n; i++){
			int pi = sa[rsa[i] - 1];
			if(cur > 0) cur--;
			for(; pi + cur < n && i + cur < n; cur++){
				if(str[pi + cur] != str[i + cur]) break;
			}
			lcp[rsa[i] - 1] = cur;
		}
	}
};

template< typename Monoid >
struct SegmentTree {
  using F = function< Monoid(Monoid, Monoid) >;

  int sz;
  vector< Monoid > seg;

  const F f;
  const Monoid M1;

  SegmentTree(int n, const F f, const Monoid &M1) : f(f), M1(M1) {
    sz = 1;
    while(sz < n) sz <<= 1;
    seg.assign(2 * sz, M1);
  }

  void set(int k, const Monoid &x) {
    seg[k + sz] = x;
  }

  void build() {
    for(int k = sz - 1; k > 0; k--) {
      seg[k] = f(seg[2 * k + 0], seg[2 * k + 1]);
    }
  }

  void update(int k, const Monoid &x) {
    k += sz;
    seg[k] = x;
    while(k >>= 1) {
      seg[k] = f(seg[2 * k + 0], seg[2 * k + 1]);
    }
  }

  Monoid query(int a, int b) {
    Monoid L = M1, R = M1;
    for(a += sz, b += sz; a < b; a >>= 1, b >>= 1) {
      if(a & 1) L = f(L, seg[a++]);
      if(b & 1) R = f(seg[--b], R);
    }
    return f(L, R);
  }

  Monoid operator[](const int &k) const {
    return seg[k + sz];
  }

  template< typename C >
  int find_subtree(int a, const C &check, Monoid &M, bool type) {
    while(a < sz) {
      Monoid nxt = type ? f(seg[2 * a + type], M) : f(M, seg[2 * a + type]);
      if(check(nxt)) a = 2 * a + type;
      else M = nxt, a = 2 * a + 1 - type;
    }
    return a - sz;
  }


  template< typename C >
  int find_first(int a, const C &check) {
    Monoid L = M1;
    if(a <= 0) {
      if(check(f(L, seg[1]))) return find_subtree(1, check, L, false);
      return -1;
    }
    int b = sz;
    for(a += sz, b += sz; a < b; a >>= 1, b >>= 1) {
      if(a & 1) {
        Monoid nxt = f(L, seg[a]);
        if(check(nxt)) return find_subtree(a, check, L, false);
        L = nxt;
        ++a;
      }
    }
    return -1;
  }

  template< typename C >
  int find_last(int b, const C &check) {
    Monoid R = M1;
    if(b >= sz) {
      if(check(f(seg[1], R))) return find_subtree(1, check, R, true);
      return -1;
    }
    int a = sz;
    for(b += sz; a < b; a >>= 1, b >>= 1) {
      if(b & 1) {
        Monoid nxt = f(seg[--b], R);
        if(check(nxt)) return find_subtree(b, check, R, true);
        R = nxt;
      }
    }
    return -1;
  }
};


int main(){
	ll n; cin >> n;
	string s; cin >> s;
	SuffixArray sa(s);
	SegmentTree<ll> seg(n,[](ll a, ll b){return min(a,b);},inf);
	rep(i,n) seg.update(i,sa.lcp[i]);
	ll ans = 0;
	rep(i,n-1){
		REP(j,i+1,n){
			ll lcp = seg.query(min(sa.ran[i],sa.ran[j]),max(sa.ran[i],sa.ran[j]));
			chmax(ans,min(lcp,j-i));
		}
	}
	cout << ans << endl;
}

Measured memory_bytes: 640