// includes {{{
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<tuple>
#include<cmath>
#include<random>
#include<cassert>
#include<bitset>
#include<cstdlib>
// #include<deque>
// #include<multiset>
// #include<cstring>
// #include<bits/stdc++.h>
// }}}
using namespace std;
using ll = long long;

// #undef DEBUG
// #define DEBUG
// DEBUG {{{
#include <array>
#include <deque>
#include <iostream>
#include <list>
#include <queue>
#include <stack>
#include <tuple>
#include <valarray>
#include <vector>
template < int n, class... T >
typename std::enable_if< (n >= sizeof...(T)) >::type __output_tuple(
    std::ostream &, std::tuple< T... > const &) {}
template < int n, class... T >
typename std::enable_if< (n < sizeof...(T)) >::type __output_tuple(
    std::ostream &os, std::tuple< T... > const &t) {
  os << (n == 0 ? "" : ", ") << std::get< n >(t);
  __output_tuple< n + 1 >(os, t);
}
template < class... T >
std::ostream &operator<<(std::ostream &os, std::tuple< T... > const &t) {
  os << "(";
  __output_tuple< 0 >(os, t);
  os << ")";
  return os;
}
template < class T, class U >
std::ostream &operator<<(std::ostream &os, std::pair< T, U > const &p) {
  os << "(" << p.first << ", " << p.second << ")";
  return os;
}
template < class T >
std::ostream &operator<<(std::ostream &os, const std::stack< T > &a) {
  os << "{";
  for(auto tmp = a; tmp.size(); tmp.pop())
    os << (a.size() == tmp.size() ? "" : ", ") << tmp.top();
  os << "}";
  return os;
}
template < class T, class Container, class Compare >
std::ostream &operator<<(std::ostream &os,
    std::priority_queue< T, Container, Compare > a) {
  os << "{ (top) ";
  while(a.size()) os << a.top() << (a.size() == 1 ? "" : ", "), a.pop();
  os << " }";
  return os;
}
template < class T, class Container >
std::ostream &operator<<(std::ostream &os, std::queue< T, Container > a) {
  os << "{ ";
  while(a.size()) os << a.front() << (a.size() == 1 ? "" : ", "), a.pop();
  os << " }";
  return os;
}
#ifdef DEBUG
#if !defined(DEBUG_OUT)
// #define DEBUG_OUT std::cerr
#endif
#define dump(...)                                                                \
  [&]() {                                                                        \
    auto __debug_tap = std::make_tuple(__VA_ARGS__);                             \
    DEBUG_OUT << "[" << __LINE__ << "] " << #__VA_ARGS__ << " = " << __debug_tap \
    << std::endl;                                                      \
  }()
template < class T >
inline void dump2D(T &d, size_t sizey, size_t sizex) {
  for(size_t i = 0; i < sizey; i++) {
    DEBUG_OUT << "\t";
    for(size_t j = 0; j < sizex; j++)
      DEBUG_OUT << d[i][j] << (j + 1 == sizex ? "" : "\t");
    DEBUG_OUT << std::endl;
  }
}
template < class T >
inline void dump1D(T &d, size_t sizey) {
  for(size_t i = 0; i < sizey; i++) {
    DEBUG_OUT << d[i] << (i + 1 == sizey ? "" : " ");
  }
  DEBUG_OUT << std::endl;
}
template <
class T, class = typename std::iterator_traits< decltype(begin(T())) >::value_type,
      class = typename std::enable_if< !std::is_same< T, std::string >::value >::type >
      std::ostream &operator<<(std::ostream &os, const T &a) {
        os << "{";
        for(auto ite = begin(a); ite != end(a); ++ite)
          os << (ite == begin(a) ? "" : ", ") << *ite;
        os << "}";
        return os;
      }
#else
#define dump(...) ((void) 42)
#define dump2D(...) ((void) 42)
#define dump1D(...) ((void) 42)
template <
class T, class = typename std::iterator_traits< decltype(begin(T())) >::value_type,
      class = typename std::enable_if< !std::is_same< T, std::string >::value >::type >
      std::ostream &operator<<(std::ostream &os, const T &a) {
        for(auto ite = begin(a); ite != end(a); ++ite)
          os << (ite == begin(a) ? "" : " ") << *ite;
        return os;
      }
#endif
// }}}


/// --- SA-IS {{{ ///
#include <string>
#include <vector>
 
static std::string push_sentinel(const std::string &v) { return v + char(); }
template < class T >
static std::vector< T > push_sentinel(std::vector< T > v) {
  v.emplace_back(0);
  return v;
}
 
template < class _T = std::string, class U = char, int K = 128 >
struct SA {
  using T = _T;
  using size_type = size_t;
  const size_type n;
  const T &s;
  std::vector< size_type > rnk;
  std::vector< size_type > sa;
  size_type operator[](size_type i) const { return sa[i]; }
  SA(const std::string &s) : n(s.size()), s(s), rnk(n) {
    sa_is< T, U >(sa, push_sentinel(s), K);
    sa.erase(begin(sa));
    for(size_type i = 0; i < n; i++) rnk[sa[i]] = i;
  }
  template < class V = std::string, class W = char >
    void sa_is(std::vector< size_type > &sa, const V &s, int k) {
      int n = s.size();
      std::vector< int > S(n); // or L
      //
      S.back() = 1;
      for(int i = n - 2; i >= 0; i--) {
        if(s[i] < s[i + 1])
          S[i] = 1;
        else if(s[i] > s[i + 1])
          S[i] = 0;
        else
          S[i] = S[i + 1];
      }
      //
      std::vector< size_type > lms;
      for(int i = 0; i < n; i++)
        if(isLMS(S, i)) lms.emplace_back(i);
      auto seed = lms;
 
      std::vector< size_type > _sa;
      inducedSort< V, W >(_sa, s, k, S, seed);
 
      sa.resize(0);
      for(auto el : _sa)
        if(isLMS(S, el)) sa.emplace_back(el);
 
      std::vector< int > nums(n, -1);
      int num = 0;
      nums[sa[0]] = 0;
 
      for(int x = 0; x < (int) sa.size() - 1; x++) {
        int i = sa[x], j = sa[x + 1];
        int diff = 0;
        for(int d = 0; d < n; d++) {
          if(s[i + d] != s[j + d] || isLMS(S, i + d) != isLMS(S, j + d)) {
            diff = 1;
            break;
          } else if(d && (isLMS(S, i + d) || isLMS(S, j + d)))
            break;
        }
        if(diff) num++;
        nums[j] = num;
      }
      auto _nums = nums;
      nums.resize(0);
      for(int el : _nums)
        if(el != -1) nums.emplace_back(el);
 
      if(num + 1 < (int) nums.size()) {
        sa_is< std::vector< int >, int >(seed, nums, num + 1);
      } else {
        seed.resize(num + 1);
        for(int i = 0; i < num + 1; i++) seed[nums[i]] = i;
      }
 
      for(size_type &el : seed) el = lms[el];
 
      inducedSort< V, W >(sa, s, k, S, seed);
    }
  template < class V = std::string, class W = char >
    void inducedSort(std::vector< size_type > &sa, const V &s, int k, const std::vector< int > &S,
        const std::vector< size_type > &lms) {
      int n = s.size();
      sa.resize(n), sa.assign(n, -1);
      std::vector< int > bin(k + 1, 0);
      for(W ch : s) bin[ch + 1]++;
      int sum = 0;
      for(int &el : bin) el = sum += el;
      // step 1
      std::vector< int > count(k);
      for(auto it = rbegin(lms); it != rend(lms); ++it) {
        int i = *it;
        W ch = s[i];
        sa[bin[ch + 1] - 1 - count[ch]] = i;
        count[ch]++;
      }
      // step 2
      count.assign(k, 0);
      for(int i : sa) {
        if(i == -1 || i == 0) continue;
        if(S[i - 1]) continue;
        W ch = s[i - 1];
        sa[bin[ch] + count[ch]] = i - 1;
        count[ch]++;
      }
      // step 3
      count.assign(k, 0);
      for(auto it = rbegin(sa); it != rend(sa); ++it) {
        int i = *it;
        if(i == -1 || i == 0) continue;
        if(!S[i - 1]) continue;
        W ch = s[i - 1];
        sa[bin[ch + 1] - 1 - count[ch]] = i - 1;
        count[ch]++;
      }
    }
  inline bool isLMS(const std::vector< int > &S, int i) {
    return i > 0 && !S[i - 1] && S[i];
  }
};
/// }}}--- ///

// require SA
/// --- LCP Array Library {{{ ///
#include <vector>
template < class S = SA<> >
struct LCP {
  using T = typename S::T;
  using size_type = size_t;
  const size_type n;
  const T &s;
  std::vector< size_type > lcp;
  int operator[](int i) const { return lcp[i]; }
  LCP(const S &sa) : n(sa.n), s(sa.s), lcp(n - 1) {
    size_type h = 0;
    for(size_type i = 0; i < n; i++) {
      if(h) h--;
      if(sa.rnk[i] == 0) continue;
      size_type j = sa[sa.rnk[i] - 1];
      while(i + h < n && j + h < n && s[i + h] == s[j + h]) h++;
      lcp[sa.rnk[i] - 1] = h;
    }
  }
};
/// }}}--- ///

// SparseTable( <data> [, default_value] )
// .fold(l, r) : [l, r)
/// --- Sparse Table {{{ ///
#include <cassert>
#include <initializer_list>
#include <iterator>
#include <vector>
template < class SemiLattice >
struct SparseTable {
public:
  using T = typename SemiLattice::T;
  using size_type = size_t;

private:
  size_type n;
  vector< size_type > log2;
  vector< vector< T > > t;
  T default_value;

public:
  SparseTable() : n(0) {}
  SparseTable(size_type n, T default_value = T())
    : n(n), log2(n + 1), default_value(default_value) {
      for(size_type i = 2; i <= n; i++) log2[i] = log2[i >> 1] + 1;
      t.resize(log2[n] + 1, vector< T >(n, default_value));
    }
  template < class InputIter, class = typename iterator_traits< InputIter >::value_type >
    SparseTable(InputIter first, InputIter last, T default_value = T())
    : SparseTable(distance(first, last), default_value) {
      copy(first, last, begin(t[0]));
      build();
    }
  SparseTable(vector< T > v, T default_value = T())
    : SparseTable(v.begin(), v.end(), default_value) {}
  SparseTable(initializer_list< T > v, T default_value = T())
    : SparseTable(v.begin(), v.end(), default_value) {}
  void set(size_type i, T val) {
    assert(i < n);
    t[0][i] = val;
  }
  T get(size_type i) {
    assert(i < n);
    return t[0][i];
  }
  void build() {
    for(size_type j = 0; j < log2[n]; j++) {
      size_type w = 1 << j;
      for(size_type i = 0; i + (w << 1) <= n; i++) {
        t[j + 1][i] = SemiLattice::op(t[j][i], t[j][i + w]);
      }
    }
  }
  T fold(size_type l, size_type r) {
    if(r - l < 1) return default_value;
    assert(l < n && r <= n);
    size_type j = log2[r - l];
    return SemiLattice::op(t[j][l], t[j][r - (1 << j)]);
  }
  size_type size() { return n; }
};
/// }}}--- ///

// SemiLattice examples {{{
struct RMQSL {
  using T = int;
  static T op(T const &a, T const &b) { return a < b ? a : b; }
};
// }}}

using SPT = SparseTable< RMQSL >;


int main() {
  std::ios::sync_with_stdio(false), std::cin.tie(0);
  int n;
  string s;
  cin >> n >> s;
  
  SA<> sa(s);
  LCP<> lcp(s);
  SPT rmq(lcp.lcp.begin(), lcp.lcp.end());

  int ans = 0;
  for(int i = 1; i < n; i++) for(int j = 0; j < i; j++) {
    int l = sa.rnk[j];
    int r = sa.rnk[i];
    if(l > r) swap(l, r);
    ans = max(ans, min(i - j, rmq.fold(l, r)));
  }
  cout << ans << endl;


  return 0;
}


Measured memory_bytes: 768