//include
//------------------------------------------
#include <vector>
#include <list>
#include <map>
#include <unordered_map>
#include <climits>
#include <set>
#include <unordered_set>
#include <deque>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <ctime>
#include <queue>
#include <random>
#include <complex>
#include <regex>
#include <locale>
#include <random>
#include <type_traits>

using namespace std;

#define SHOW_VECTOR(v) {std::cerr << #v << "\t:";for(const auto& xxx : v){std::cerr << xxx << " ";}std::cerr << "\n";}
#define SHOW_MAP(v){std::cerr << #v << endl; for(const auto& xxx: v){std::cerr << xxx.first << " " << xxx.second << "\n";}}

using LL = long long;

//------------------------------------------
//------------------------------------------

template<typename T>
struct SparseTable {
    int n;
    int h;
    vector<T> a;

    /// @param log_table log_table[k] = floor(log(k))
    vector<T> log_table;

    /// @brief 0-index original
    /// @param table table[i][k] = iから2^kの範囲のargmin
    vector<vector<T>> table;
    SparseTable() = default;
    SparseTable(const vector<T> a) {
        init(a);
    }

    void init(vector<T> _a) {
        a = _a;
        n = a.size();
        h = 0;
        // nをなめるために必要な幅 ceil(log(n/2))
        while ((1 << h) < n) ++h;
        h++;
        table = vector<vector<T>>(1 << h, vector<T>(h));
        log_table = vector<T>(n + 1, 0);
        for (int i = 2; i <= n; i++) log_table[i] = log_table[i >> 1] + 1;
        for (int i = 0; i < n; i++) table[i][0] = i;
        for (int k = 1; k < h; k++) {
            for (int i = 0; i < n; i++) {
                int left = table[i][k - 1];
                int right = table[min(i + (1 << (k - 1)), n - 1)][k - 1];
                if (a[left] < a[right]) table[i][k] = left;
                else table[i][k] = right;
            }
        }
    }

    /// @param l original close [
    /// @param r original open )
    /// @brief argmin a[l, r)
    T get(int l, int r) {
        int lp = table[l][log_table[r - l]];
        int rp = table[r - (1 << log_table[r - l])][log_table[r - l]];
        if (a[lp] < a[rp]) return lp;
        else return rp;
    }

    void show() {
        for (int k = 0; k < h; k++) {
            printf("k = %d\n", k);
            for (int i = 0; i < n; i++) {
                printf("%3d ", table[i][k]);
            }
            printf("\n\n");
        }
    }

};

/// @details n (logn)^2
class Suffix_Array {
private:
    int n;

    /// @brief |S[0:]|=n
    /// @brief |S[n:]|=0
    string S;

    /// @brief 0-index
    /// @details SA[j] = S[i:]の接尾辞の昇順ソートされた元の位置i
    /// @details SA[0] = ""
    vector<int> SA;

    /// @brief 0-index
    /// @details rank[SA[j]] = kのrank分かっている時の2kのrank比較 元の位置S[(i=SA[j]):]のランク
    vector<int> rank;

    /// @details rank計算用
    vector<int> tmp;

    bool lb_substr(const string &pat, int si = 0, int ti = 0) {
        int sn = n;
        int tn = pat.size();
        while (si < sn && ti < tn) {
            if (S[si] < pat[ti]) return true;
            if (S[si] > pat[ti]) return false;
            ++si, ++ti;
        }
        return si >= sn && ti < tn;
    }

public:

    Suffix_Array(const string s) {
        S = s;
        n = s.size();
        SA = vector<int>(n + 1);
        rank = vector<int>(n + 1);
        tmp = vector<int>(n + 1);
        construct();
    }

    void construct() {

        int k = 1;

        /// @param i 元の文字列Sの位置i S[i:]
        /// @param j 元の文字列Sの位置j S[j:]
        auto comp = [&](int i, int j) {
            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;
            }
        };

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

        for (k = 1; k <= n; k *= 2) {
            sort(SA.begin(), SA.end(), comp);

            tmp[SA[0]] = 0;
            for (int i = 1; i <= n; i++) tmp[SA[i]] = tmp[SA[i - 1]] + (comp(SA[i - 1], SA[i]) ? 1 : 0);
            for (int i = 0; i <= n; i++) rank[i] = tmp[i];
        }
    }

    /// @param pat Sに含まれるか調べる文字列パターン
    /// @brief O(|pat|log|S|)
    bool contain(const string &pat) {
        int low = 0;
        int high = n;
        while (high - low > 1) {
            int c = (low + high) / 2;
            if (S.compare(SA[c], pat.length(), pat) < 0) low = c; // S[SA[c]:] < T
            else high = c; // S[SA[c]:] >= T
        }
        return S.compare(SA[high], pat.length(), pat) == 0;
    }

    /// @param pat 文字列パターン
    /// @details 1 ~ n 正常(0は空文字)
    /// @details n + 1 .end()
    /// @details SA[high:]が下限
    int lower_bound(const string &pat) {
        int low = 0;
        int high = n + 1;
        while (high - low > 1) {
            int mid = (low + high) / 2;
            if (lb_substr(pat, SA[mid])) low = mid;
            else high = mid;
        }
        return high;
    }

    /// @param pat 文字列パターン
    /// @details 1 ~ n 正常(0は空文字)
    /// @details n + 1 .end()
    /// @details SA[:high)が上限
    int upper_bound(string &pat) {
        int low = 0;
        int high = n + 1;
        pat.back()++;
        while (high - low > 1) {
            int mid = (low + high) / 2;
            if (lb_substr(pat, SA[mid])) low = mid;
            else high = mid;
        }
        pat.back()--;
        return high;
    }

    vector<int> get_SA() const {
        return SA;
    }

    string get_S() const {
        return S;
    }

    void show_SA() {
        printf("alphabetical order\n");
        printf("i :");
        for (int i = 0; i <= n; i++)printf("%3d", i);
        printf("\n");
        printf("SA:");
        for (int i = 0; i <= n; i++)printf("%3d", SA[i]);
        printf("\n");
        printf("L :");
        for (int i = 0; i <= n; i++)printf("%3d", n - SA[i]);
        printf("\n\n");
    }

};

class Longest_Common_Prefix_Array {
private:
    int n;
    const Suffix_Array &suffixArray;

    /// @param LCP[i] = SAでi,i+1番目に小さい文字列の最長共通接頭辞
    vector<int> LCP;

    /// @param rank[i] = S[i:]の文字列はSAで何番目に小さいか
    vector<int> rank;

    SparseTable<int> sparseTable;

public:
    Longest_Common_Prefix_Array(const Suffix_Array &suffixArray)
            : suffixArray(suffixArray) {
        n = suffixArray.get_S().size();
        LCP = vector<int>(n + 1);
        rank = vector<int>(n + 1);
        construct();
    }

    void construct() {
        const vector<int> &SA = suffixArray.get_SA();
        const string S = suffixArray.get_S();
        for (int i = 0; i <= n; i++) rank[SA[i]] = i;

        //S[i:]と S[i:]より文字列順で1小さい文字列とのlcp長
        int h = 0;

        //0番目に小さい文字列は""より、0
        LCP[0] = 0;

        // Sの先頭|S[i:]|=nから
        for (int i = 0; i < n; i++) {

            //S[i:]より1小さい文字列のS上の位置
            int j = SA[rank[i] - 1];

            if (h > 0) h--;

            //後ろが一致してるだけ増やす
            while (j + h < n && i + h < n) {
                if (S[j + h] != S[i + h]) break;
                h++;
            }

            LCP[rank[i] - 1] = h;
        }

        sparseTable = SparseTable<int>(LCP);
    }

    vector<int> get_LCP() {
        return LCP;
    }

    int operator[](int k) const {
        return LCP[k];
    }

    /// @param i original S[i:]
    /// @param j original S[j:]
    int common(int i, int j) {
        if (i == j)return n - i;
        else {
            int l = rank[i];
            int r = rank[j];
            if (l > r) swap(l, r);
            return LCP[sparseTable.get(l, r)];
        }
    }

    void show() {
        printf("alphabetical order\n");
        printf("i :");
        for (int i = 0; i <= n; i++)printf("%3d", i);
        printf("\n");
        printf("LC:");
        for (int i = 0; i <= n; i++)printf("%3d", LCP[i]);
        printf("\n\n");
    }

    void all_show() {
        const vector<int> &SA = suffixArray.get_SA();
        printf("alphabetical order\n");
        printf("i :");
        for (int i = 0; i <= n; i++)printf("%3d", i);
        printf("\n");
        printf("SA:");
        for (int i = 0; i <= n; i++)printf("%3d", SA[i]);
        printf("\n");
        printf("LC:");
        for (int i = 0; i <= n; i++)printf("%3d", LCP[i]);
        printf("\n");
        printf("RK:");
        for (int i = 0; i <= n; i++)printf("%3d", rank[i]);
        printf("\n\n");
    }

};

int main() {

    int N;
    cin >> N;

//    vector<int> a = {
//            8, 2, 3
//    };
//
//    SparseTable<int> sTable(a);
//    sTable.show();
//
//    int Q;
//    cin >> Q;
//
//    while (Q--) {
//        int l, r;
//        cin >> l >> r;
//        int k = sTable.get(l, r);
//        cout << k << " " << a[k] << endl;
//    }


    string S;
    cin >> S;

    Suffix_Array suffixArray(S);
    Longest_Common_Prefix_Array longestCommonPrefixArray(suffixArray);
   // longestCommonPrefixArray.all_show();

    int ans = 0;
    for (int i = 0; i < N; i++) {
        for (int j = i; j < N; j++) {
            ans = max(ans, min(j - i, longestCommonPrefixArray.common(i, j)));
        }
    }

    cout << ans << endl;

  //  cout << longestCommonPrefixArray.common(0, 1) << endl;

    return 0;
}




































Measured memory_bytes: 1920