#include <iostream>
#include <vector>
#include <array>
#include <set>
#include <cmath>
#include <string>
#include <queue> 
#include <list>
#include <stack>
#include <unordered_map>
#include <algorithm> 
#include <numeric>  
#include <functional>
#include <cassert>
#include <bitset>
#include <cctype>  
#include <iomanip> 
#include <limits>  
// std::numeric_limits<int>::max(), min()
// std::setprecision(X)
// std::islower(), std::isupper(), std::tolower, std::toupper
// std::accumulate(m.begin(), m.end(), 0LL);
// std::sort(begin(),end()) // 0, 1, 2, 3 ,4 (default, less);
// std::priority_queue<int, vector<int>, less<int>> que; // 4, 3, 2, 1 (defualt, less) : 
 
#define rep(i, n)  for(int i = 0; i < (int)(n);    i++)
#define sz(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()

template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return 1; } return 0; }
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return 1; } return 0; }
typedef long long ll;

ll gcd ( ll a, ll b ) { if ( b == 0 ) return a; else return gcd ( b, a%b ); }
ll lcm ( ll a, ll b ) { return a * b / gcd(a, b); }



template <class Monoid>  class SegmentTree {
  private:
    using T = typename Monoid::T;
    using size_type = std::size_t;
    size_type n;
    std::vector< T > data;
    void prop(int i) { data[i] = Monoid::op(data[2 * i], data[2 * i + 1]); }

  public:
    SegmentTree() : n(0) {}

    SegmentTree(int n, T initial = Monoid::identity()) : n(n) {
      data.resize(n * 2, initial);
      for(int i = n - 1; i > 0; i--) data[i] = Monoid::op(data[i * 2], data[i * 2 + 1]);
    }

    template <class InputIter, class = typename std::iterator_traits<InputIter>::value_type>
    SegmentTree(InputIter first, InputIter last) : SegmentTree(distance(first, last)) {
      copy(first, last, data.begin() + n);
      for(int i = n - 1; i > 0; i--) prop(i);
    }

    SegmentTree(std::vector< T > v) : SegmentTree(v.begin(), v.end()) {}

    SegmentTree(std::initializer_list< T > v) : SegmentTree(v.begin(), v.end()) {}

    void set(size_t i, const T &v) {
      data[i += n] = v;
      while(i >>= 1) prop(i);
    }
    
    void add(size_t i, const T &v) {
      data[i += n] += v;
      while(i >>= 1) prop(i);
    }

    T get(size_t i) {
      return data[i + n];
    }

    T fold(int l, int r) {
      if(l < 0) l = 0;
      if(l >= r) return Monoid::identity();
      if(r > static_cast< int >(n)) r = n;
      T tmpL = Monoid::identity(), tmpR = Monoid::identity();
      for(l += n, r += n; l < r; l >>= 1, r >>= 1) {
        if(l & 1) tmpL = Monoid::op(tmpL, data[l++]);
        if(r & 1) tmpR = Monoid::op(data[--r], tmpR);
      }
      return Monoid::op(tmpL, tmpR);
    }

    size_type size() { return n; }
};

template < class U = long long > struct RangeMin {
  using T = U;
  static T op(const T &a, const T &b) { return std::min< T >(a, b); }
  static constexpr T identity() { return std::numeric_limits<T>::max(); }
};


class SuffixArray {
  private:
    std::string str;
    std::vector<int> sa;
    std::vector<int> rank;
    std::vector<int> lcp;
    SegmentTree<RangeMin<int>> st;

    // Manber & Myers algorithm ( O(N logN logN)), to use SA-IS O(N), check ref.
    void ManberMyersAlgorithm() {
      const int n = str.size();
      rank.resize(n+1); sa.resize(n+1);

      for(int i=0; i<=n; i++) {
        sa[i] = i;
        rank[i] = i < n ? str[i] : -1;
      }

      std::vector<int> tmp(n+1);
      for(int k=1; k<=n; k*=2) {
        auto compare_sa = [&](int i, int j) -> bool {
          if ( rank[i] != rank[j] ) return rank[i] < rank[j];
          else {
            int ri = i + k <= n ? rank[i+k] : -1;
            int rj = j + k <= n ? rank[j+k] : -1;
            return ri < rj;
          }
        };
        std::sort(sa.begin(), sa.end(), compare_sa);
        tmp[sa[0]] = 0;
        for(int i=1; i<=n; i++) {
          tmp[sa[i]] = tmp[sa[i-1]] + (compare_sa(sa[i-1], sa[i]) ? 1 : 0 );
        }
        for(int i=0; i<=n; i++) {
          rank[i] = tmp[i];
        }
      }
      return;
    }

    void makeLCP() {
      const int n = str.size();
      lcp.resize(n);
      int h = 0; lcp[0] = 0;
      for(int i=0; i<n; i++) {
        int j = sa[rank[i]-1];
        if(h>0) h--;
        for(; j+h<n && i+h<n; h++) {
          if( str[j+h] != str[i+h] ) break;
        }
        lcp.at(rank[i]-1) = h;
      }
      return;
    }

    void makeSegTree(){
      const int n = str.size();
      for(int suffix=0; suffix<n; suffix++) {
        st.set(suffix, lcp.at(suffix));
      }
      return;
    }


  public:
    SuffixArray( std::string s_ ) : st(s_.size()) {
      str = s_;
      ManberMyersAlgorithm();
      makeLCP();
      makeSegTree();
    }

    std::string GetSAstr(int i) {
      return str.substr(sa.at(i));
    }

    int GetSA(int i) {
      return sa.at(i);
    }

    int GetLCP(int i, int j) {
      int irsa = GetRank(i);
      int jrsa = GetRank(j);
      if ( irsa > jrsa ) std::swap(irsa, jrsa);
      return st.fold(i,j);
      //return st.get(i);
    }

    int GetRank(int i) {
      return rank.at(i);
    }

};




class Z_Algorithm {
  private:
    int N;
    std::string str;
  public:
    Z_Algorithm (const std::string& str_) {
      str = str_;
      N = str.size();
    }

    auto Calc () {
      std::vector<int> res(N);
      res[0] = N;
      int i = 1, j = 0;
      while (i < N) {
        while (i+j < N && str[j] == str[i+j]) ++j;
        res[i] = j;
        if (j == 0) {++i; continue;}
        int k = 1;
        while (i+k < N && k+res[k] < j) res[i+k] = res[k], ++k;
        i += k, j -= k;
      }
      return res;
    }
};




//////////////////////////////////////
void solveSA (const std::string& S) {
  SuffixArray sa(S);

  int ans = 0;
  for(int i=0; i<sz(S); i++) {
    //std::cout << i << " " << sa.GetSAstr(i) << " " << sa.GetLCP(i,i+1) << std::endl;
    for(int j=i+1; j<=sz(S); j++) {
      int irsa = sa.GetRank(i);
      int jrsa = sa.GetRank(j);
      if ( irsa > jrsa ) std::swap(irsa, jrsa);
      int lcp = sa.GetLCP(irsa,jrsa); lcp = std::min(lcp, j-i);
      ans = std::max(ans, lcp);
    }
  }
  std::cout << ans << std::endl;
  return;
}


//////////////////////////////////////
void solveZ (const std::string& S) {
  int res = 0;
  for (int i = 0; i < sz(S); ++i) {
    std::string T = S.substr(i);
    Z_Algorithm z(T);
    auto lcp = z.Calc();

    for (int j = 0; j < T.size(); ++j) {
      int l = std::min(lcp[j], j);
      res = std::max(res, l);
    }
  }
  std::cout << res << std::endl;
  return;
}

//////////////////////////////////////
/*
void solveDP(const std::string& S) {
  const int N = sz(S);
  std::vector<std::vector<int>> dp(N+1, std::vector<int>(N+1, -1));
  auto f = [&] (int i, int j) -> int {
    if ( i == 0 || j == 0 ) return 0;
    if ( dp[i][j] != -1 ) return dp[i][j];
    if ( S[i] != S[j] ) return dp[i][j] = 0;
    else return dp[i][j] = f(i-1,j-1) + 1;
  };
  return;
}
*/

/////////////////////////////////////
void solveBS(const std::string &S) {
  return;
}




int main (void) {
  std::cin.tie(0);  std::ios::sync_with_stdio(false);

  int N; std::cin >> N;
  std::string S; std::cin >> S;

  solveSA(S);
  //solveZ(S);

  return 0;
}


Measured memory_bytes: 384