#include <algorithm>
#include <functional>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
using namespace std;

#define FOR(i,m,n) for(int i=(m);i<(n);++i)
#define REP(i,n) FOR(i,0,n)
#define ALL(v) (v).begin(),(v).end()
/*-------------------------------------------------*/
template <typename T = string>
struct SuffixArray {
  vector<int> sa, rank, lcp;

  SuffixArray(const T &s) : str(s) {
    int n = str.size();
    str.push_back(' ');
    sa.resize(n + 1);
    iota(ALL(sa), 0);
    sort(ALL(sa), [&](int lhs, int rhs) { return s[lhs] == s[rhs] ? lhs > rhs : s[lhs] < s[rhs]; });
    rank.resize(n + 1);
    vector<int> tmp_rank(n + 1), tmp_sa(n + 1), cnt(n + 1);
    REP(i, n + 1) rank[i] = s[i];
    for (int len = 1; len <= n; len <<= 1) {
      REP(i, n + 1) {
        if (i > 0 && rank[sa[i - 1]] == rank[sa[i]] && sa[i - 1] + len <= n && rank[sa[i - 1] + len / 2] == rank[sa[i] + len / 2]) {
          tmp_rank[sa[i]] = tmp_rank[sa[i - 1]];
        } else {
          tmp_rank[sa[i]] = i;
        }
      }
      copy(ALL(sa), tmp_sa.begin());
      iota(ALL(cnt), 0);
      REP(i, n + 1) {
        int idx = tmp_sa[i] - len;
        if (idx >= 0) sa[cnt[tmp_rank[idx]]++] = idx;
      }
      rank.swap(tmp_rank);
    }

    lcp.resize(n);
    int common = 0;
    REP(i, n) {
      int j = sa[rank[i] - 1];
      if (common > 0) --common;
      for (; i + common < n && j + common < n; ++common) {
        if (str[i + common] != str[j + common]) break;
      }
      lcp[rank[i] - 1] = common;
    }
  }

  vector<int> match(T &t) {
    int lb = lower_bound(t), ub = upper_bound(t);
    vector<int> res(ub - lb);
    copy(sa.begin() + lb, sa.begin() + ub, res.begin());
    sort(ALL(res));
    return res;
  }

private:
  T str;

  bool cmp(const string &t, int str_idx = 0, int t_idx = 0) {
    int str_sz = str.size(), t_sz = t.size();
    for (; str_idx < str_sz && t_idx < t_sz; ++str_idx, ++t_idx) {
      if (str[str_idx] < t[t_idx]) return 1;
      if (str[str_idx] > t[t_idx]) return 0;
    }
    return str_idx == str_sz && t_idx < t_sz;
  }

  int lower_bound(const T &t) {
    int lb = 0, ub = str.size();
    while (ub - lb > 1) {
      int mid = (lb + ub) >> 1;
      (cmp(t, sa[mid], 0) ? lb : ub) = mid;
    }
    return ub;
  }

  int upper_bound(T &t) {
    ++t.back();
    int res = lower_bound(t);
    --t.back();
    return res;
  }
};

template <typename MeetSemilattice>
struct SparseTable {
  using Fn = function<MeetSemilattice(MeetSemilattice, MeetSemilattice)>;

  SparseTable(const vector<MeetSemilattice> &a, const Fn fn) : fn(fn) {
    int n = a.size(), table_h = 0;
    lg.assign(n + 1, 0);
    FOR(i, 2, n + 1) lg[i] = lg[i >> 1] + 1;
    while ((1 << table_h) <= n) ++table_h;
    dat.assign(table_h, vector<MeetSemilattice>(n));
    REP(j, n) dat[0][j] = a[j];
    FOR(i, 1, table_h) for (int j = 0; j + (1 << i) <= n; ++j) {
      dat[i][j] = fn(dat[i - 1][j], dat[i - 1][j + (1 << (i - 1))]);
    }
  }

  MeetSemilattice query(int left, int right) {
    int h = lg[right - left];
    return fn(dat[h][left], dat[h][right - (1 << h)]);
  }

private:
  const Fn fn;
  vector<int> lg;
  vector<vector<MeetSemilattice> > dat;
};

int main() {
  cin.tie(nullptr); ios::sync_with_stdio(false);
  // freopen("input.txt", "r", stdin);

  int n; cin >> n;
  string s; cin >> s;
  SuffixArray<> suffix(s);
  SparseTable<int> st(suffix.lcp, [](int lhs, int rhs) { return min(lhs, rhs); });
  int ans = 0;
  REP(i, n) FOR(j, i + 1, n) {
    int l = suffix.rank[i], r = suffix.rank[j];
    if (l > r) swap(l, r);
    ans = max(ans, min(st.query(l, r), j - i));
  }
  cout << ans << '\n';
  return 0;
}


Measured memory_bytes: 640