#include <iostream>
#include <fstream>
#include <cmath>
#include <string>
#include <vector>
#include <map>
#include <queue>
#include <set>
#include <unordered_set>

typedef long long ll;
using std::vector;
template<typename T> void ndarray(vector<T> &vec, int len) { vec.resize(len); }
template<typename T, typename... Args> void ndarray(vector<T> &vec, int len, Args... args) {
    vec.resize(len);
    for (auto &v : vec) ndarray(v, args...);
}

//////////////////////////////////////

template <typename T>
struct Rolli {
    //文字列と部分文字列のハッシュを得るためのstruct
    T base;
    vector<T> hash; //hash[i] = s[0] * base^(i-1) + s[1] * base^(i-2) + ... + s[i-1]
    vector<T> pow;  // pow[i] = base^(i)
    //lからlen文字の部分文字列のハッシュを得る
    //0 <= l <= N-1, 0 <= l+len-1 < N
    T get_hash(int l, int len) {
        int r = l + len;
        return hash[r] - hash[l] * pow[r - l];
    }
    
    Rolli(const std::string &s, T base) : base(base)
    {
        auto n = s.length();
        hash.resize(n+1);
        pow.resize(n+1, 1);
        for (int i = 0; i < n; i++) {
            pow[i+1] = pow[i] * base;
            hash[i+1] = hash[i] * base + s[i];
        }
    }
};
//ハッシュ衝突防止用long long２つのハッシュ
//baseにはDoubleHash base(1009, 1007)などを用いるといい
//なお、pairは比較可能なので、そのままmapのキーとして使える
using pll = std::pair<ll, ll>;
struct DoubleHash : public pll
{
    static pll MODs;;
    DoubleHash(pll x) : pll(x) {}
    DoubleHash(ll x, ll y) : pll(x, y) {}
    DoubleHash(ll x) : DoubleHash(x, x) {}
    DoubleHash() : DoubleHash(0) {}
    static inline DoubleHash modsub(pll x) {
        if (x.first >= MODs.first) {
            x.first -= MODs.first;
        }
        if (x.second >= MODs.second) {
            x.second -= MODs.second;
        }
        return x;
    }
    
    DoubleHash operator+(const DoubleHash &x) const {
        return modsub(pll(this->first + x.first, this->second + x.second));
    }
    DoubleHash operator+(ll x) const {
        return modsub(pll(this->first + x, this->second + x));
    }
    DoubleHash operator-(const DoubleHash &x) const {
        return modsub(pll(this->first - x.first + MODs.first, this->second - x.second + MODs.second));
    }
    DoubleHash operator*(const DoubleHash &x) const {
        return modsub(pll(this->first * x.first % MODs.first, this->second * x.second % MODs.second));
    }
    DoubleHash operator*(ll x) const {
        return modsub(pll(this->first * x % MODs.first, this->second * x % MODs.second));
    }
    
    ll get_ll() {
        return this->first * 1e9 + this->second;
    }
};
pll DoubleHash::MODs = pll(1000000007, 998244353);

int main() {
    //std::ifstream in("0_normal_2");
    //std::cin.rdbuf(in.rdbuf());
    
    int N;
    std::cin >> N;
    
    std::string S;
    std::cin >> S;
    
    DoubleHash base(1009, 1007);
    
    Rolli<DoubleHash> rl(S, base);
    
    for (int len = N/2 + 1; len > 0; len--) {
        std::unordered_set<ll> us;
        
        for (int i = 0; i < N; i++) {
            //iからlenの部分文字列のハッシュが登録済みか調べる
            //今回追加されるものは含んではいけないのでこちらが先
            if (i + len - 1 < N) {
                auto h = rl.get_hash(i, len);
                if (us.count(h.get_ll()) > 0) {
                    std::cout << len << std::endl;
                    return 0;
                }
            }
            //iまで長さlen つまり、i - len + 1からlenの部分文字列のハッシュを登録する
            if (i - len + 1 >= 0) {
                auto h = rl.get_hash(i - len + 1, len);
                us.insert(h.get_ll());
            }
            
        }
    }
    std::cout << 0 << std::endl;
    return 0;
}


Measured memory_bytes: 736