using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class Contest
{
	public static void Main()
	{


		int  n;
		string s;
		MyIO.ReadMulti(out n);
		MyIO.ReadMulti(out s);

		SuffixArray sa = new SuffixArray(s);
		sa.BuildLCP();
		int max = 0;
		for(int i=0;i<n-1;i++){
			for(int j=i+1;j<n;j++){
				int g=j-i;
				int c = sa.GetLCP(i,j);
				if(c<=g&&(max<c)) max=c;

			}
		}

		MyIO.WriteMulti(max);




		
	}

public class SuffixArray {
    int[] S;
    int N;
    int[] SA;
    int[] rank;
    sparseTableRMQ rmq;
    public SuffixArray(string str) {
        N = str.Length;
        S = new int[N + 1];
        for (int i = 0; i < N; i++)
            S[i] = str[i];
        SA = sais(S, S.Max());
        rank = new int[N + 1];
        for (int i = 0; i <= N; i++) rank[SA[i]] = i;
    }

    void createBeginBucket(int[] v, int[] b) {
        for (int i = 0; i < b.Length; i++) b[i] = 0;
        for (int i = 0; i < v.Length; i++) b[v[i]]++;
        int sum = 0;
        for (int i = 0; i < b.Length; i++) { b[i] += sum; var tmp = b[i]; b[i] = sum; sum = tmp; }
    }
    void createEndBucket(int[] v, int[] b) {
        for (int i = 0; i < b.Length; i++) b[i] = 0;
        for (int i = 0; i < v.Length; i++) b[v[i]]++;
        for (int i = 1; i < b.Length; i++) b[i] += b[i - 1];
    }
    void inducedSort(int[] v, int[] sa, int mv, int[] b, bool[] isl) {
        createBeginBucket(v, b);
        for (int i = 0; i < v.Length; i++) if (sa[i] > 0 && isl[sa[i] - 1]) sa[b[v[sa[i] - 1]]++] = sa[i] - 1;
    }
    void invertInducedSort(int[] v, int[] sa, int mv, int[] b, bool[] isl) {
        createEndBucket(v, b);
        for (int i = v.Length - 1; i >= 0; i--)
            if (sa[i] > 0 && !isl[sa[i] - 1]) sa[--b[v[sa[i] - 1]]] = sa[i] - 1;
    }
    int[] sais(int[] v, int mv) {
        if (v.Length == 1) return new int[] { 0 };
        var isl = new bool[v.Length];
        var b = new int[mv + 1];
        var sa = new int[v.Length];
        for (int i = 0; i < v.Length; i++)
            sa[i] = -1;
        for (int i = v.Length - 2; i >= 0; i--)
            isl[i] = v[i] > v[i + 1] || (v[i] == v[i + 1] && isl[i + 1]);
        createEndBucket(v, b);
        for (int i = 0; i < v.Length; i++) if (isLMS(i, isl)) sa[--b[v[i]]] = i;
        inducedSort(v, sa, mv, b, isl);
        invertInducedSort(v, sa, mv, b, isl);


        var cur = 0;
        var ord = new int[v.Length];
        for (int i = 0; i < v.Length; i++) if (isLMS(i, isl)) ord[i] = cur++;
        var next = new int[cur];
        cur = -1;
        int prev = -1;
        for (int i = 0; i < v.Length; i++)
        {
            if (!isLMS(sa[i], isl)) continue;
            var diff = false;
            for (int d = 0; d < v.Length; d++)
            {
                if (prev == -1 || v[sa[i] + d] != v[prev + d] || isl[sa[i] + d] != isl[prev + d])
                {
                    diff = true; break;
                }
                else if (d > 0 && isLMS(sa[i] + d, isl)) break;
            }
            if (diff) { cur++; prev = sa[i]; }
            next[ord[sa[i]]] = cur;
        }
        var reord = new int[next.Length];
        for (int i = 0; i < v.Length; i++) if (isLMS(i, isl)) reord[ord[i]] = i;
        var nextsa = sais(next, cur);
        createEndBucket(v, b);
        for (int i = 0; i < sa.Length; i++) sa[i] = -1;
        for (int i = nextsa.Length - 1; i >= 0; i--) sa[--b[v[reord[nextsa[i]]]]] = reord[nextsa[i]];
        inducedSort(v, sa, mv, b, isl);
        invertInducedSort(v, sa, mv, b, isl);
        return sa;
    }
    bool isLMS(int x, bool[] isl) { return x > 0 && isl[x - 1] && !isl[x]; }

    public void BuildLCP() {
        var k = 0;
        var h = new int[N];
        for (int i = 0; i < N; i++)
        {
            var j = SA[rank[i] - 1];
            if (k > 0) k--;
            for (; j + k < N && i + k < N; k++) if (S[j + k] != S[i + k]) break;
            h[rank[i] - 1] = k;
        }
        rmq = new sparseTableRMQ(h);
    }
    /// <summary>
    /// s[<paramref name="i"/>:] と s[<paramref name="j"/>:] の最大共通接頭辞を O(loglogN) で計算します。
    /// </summary>
    /// <returns></returns>
    public int GetLCP(int i, int j) {
        i = rank[i]; j = rank[j];
        return rmq.Query(Math.Min(i, j), Math.Max(i, j));
    }
    /// <summary>
    /// rankがiのものを返す
    /// </summary>
    public int this[int index] {
        get { return index == 0 ? N : SA[index - 1]; }
    }
    /// <summary>
    /// s[i:]のランクを返す
    /// </summary>
    public int Rank(int index) {
        return rank[index];
    }

    public class sparseTableRMQ {
        int n;
        int[] A;
        public sparseTableRMQ(int[] a) {
            var k = 1;
            n = a.Length;
            for (int i = 1; i < n; i <<= 1) k++;

            A = new int[n * k];
            for (int i = 0; i < n; i++)
                A[i] = a[i];
            var d = 0;
            for (int i = 1; i < n; i <<= 1, d += n)
            {
                for (int j = 0; j < n; j++)
                    A[d + n + j] = A[d + j];
                for (int j = 0; j < n - i; j++)
                    A[d + n + j] = Math.Min(A[d + j], A[d + j + i]);
            }
        }
        /// <summary>
        /// value of [l,r)
        /// </summary>
        public int Query(int l, int r) {
            r--;
            int z = r - l, k = 0, e = 1, s;
            s = ((z & 0xffff0000) != 0 ? 1 : 0) << 4; z >>= s; e <<= s; k |= s;
            s = ((z & 0x0000ff00) != 0 ? 1 : 0) << 3; z >>= s; e <<= s; k |= s;
            s = ((z & 0x000000f0) != 0 ? 1 : 0) << 2; z >>= s; e <<= s; k |= s;
            s = ((z & 0x0000000c) != 0 ? 1 : 0) << 1; z >>= s; e <<= s; k |= s;
            s = ((z & 0x00000002) != 0 ? 1 : 0) << 0; z >>= s; e <<= s; k |= s;
            //var vl = A[l + n * k];
            //var vr = A[r - e + 1 + (n * k)];
            return Math.Min(A[l + (n * k)], A[r + (n * k) - e + 1]);
        }
    }
}





}









public struct VT<T, U>
{
	public T Item1; public U Item2;
    public VT(T a, U b) { Item1 = a; Item2 = b; }
}
public struct VT<T, U, V>
{
	public T Item1; public U Item2; public V Item3;
    public VT(T a, U b, V c) { Item1 = a; Item2 = b; Item3 = c; }
}
public struct VT<T, U, V, W>
{
	public T Item1; public U Item2; public V Item3; public W Item4;
    public VT(T a, U b, V c, W d) { Item1 = a; Item2 = b; Item3 = c; Item4 = d; }
}

public class Printer : System.IO.StreamWriter
{
	public override IFormatProvider FormatProvider { get { return System.Globalization.CultureInfo.InvariantCulture; } }
	public Printer(System.IO.Stream stream) : base(stream, new UTF8Encoding(false, true)) { }
	public Printer(System.IO.Stream stream, Encoding encoding) : base(stream, encoding) { }
}
public static class MyIO
{
	private static char[] splitter = new char[] {' '};
	private static bool eq<T, U>() => typeof(T).Equals(typeof(U));
	private static T ct<T, U>(U a) => (T)Convert.ChangeType(a, typeof(T));
	private static T cv<T>(string s) => eq<T, int>()    ? ct<T, int>(int.Parse(s))
	                                  : eq<T, long>()   ? ct<T, long>(long.Parse(s))
	                                  : eq<T, double>() ? ct<T, double>(double.Parse(s, System.Globalization.CultureInfo.InvariantCulture))
	                                  : eq<T, char>()   ? ct<T, char>(s[0])
	                                                    : ct<T, string>(s);				
	public static string[] ReadMulti<T>(out T a) {
		var ar = Console.ReadLine().Split(splitter, StringSplitOptions.RemoveEmptyEntries); 
		a = cv<T>(ar[0]);
		return ar;
	}
	public static string[] ReadMulti<T, U>(out T a, out U b) {
		var ar = ReadMulti<T>(out a); 
        b = cv<U>(ar[1]);
		return ar;
	}
	public static string[] ReadMulti<T, U, V>(out T a, out U b, out V c) {
		var ar = ReadMulti<T, U>(out a, out b); 
        c = cv<V>(ar[2]);
		return ar;
	}
	public static string[] ReadMulti<T, U, V, W>(out T a, out U b, out V c, out W d) {
		var ar = ReadMulti<T, U, V>(out a, out b, out c); 
        d = cv<W>(ar[3]);
		return ar;
	}
	public static string[] ReadMulti<T, U, V, W, X>(out T a, out U b, out V c, out W d, out X e) {
		var ar = ReadMulti<T, U, V, W>(out a, out b, out c, out d); 
        e = cv<X>(ar[4]);
		return ar;
	}
	public static string[] ReadMulti<T, U, V, W, X, Y>(out T a, out U b, out V c, out W d, out X e, out Y f) {
		var ar = ReadMulti<T, U, V, W, X>(out a, out b, out c, out d, out e); 
        f = cv<Y>(ar[5]);
		return ar;
	}
	public static string[] ReadArray<T>(out T[] a) {		
		var ar = Console.ReadLine().Split(splitter, StringSplitOptions.RemoveEmptyEntries);
		a = ar.Select(cv<T>).ToArray();
		return ar;
	}		
	public static string[][] ReadLines<T>(int n, out T[] a) {
		a = new T[n];
		string[][] arr = new string[n][];
		for(int i=0; i<n; i++) arr[i] = ReadMulti(out a[i]);
		return arr;
	}
	public static string[][] ReadLines<T, U>(int n, out VT<T, U>[] a) {
		a = new VT<T, U>[n];
		string[][] arr = new string[n][];
		for(int i=0; i<n; i++) {
			T o; U p;
			arr[i] = ReadMulti(out o, out p);
			a[i] = new VT<T, U>(o, p);
		}
		return arr;
	}
	public static string[][] ReadLines<T, U, V>(int n, out VT<T, U, V>[] a) {
		a = new VT<T, U, V>[n];
		string[][] arr = new string[n][];
		for(int i=0; i<n; i++) {
			T o; U p; V q;
			arr[i] = ReadMulti(out o, out p, out q);
			a[i] = new VT<T, U, V>(o, p, q);
		}
		return arr;
	}
	public static string[][] ReadLines<T, U, V, W>(int n, out VT<T, U, V, W>[] a) {
		a = new VT<T, U, V, W>[n];
		string[][] arr = new string[n][];
		for(int i=0; i<n; i++) {
			T o; U p; V q; W r;
			arr[i] = ReadMulti(out o, out p, out q, out r);
			a[i] = new VT<T, U, V, W>(o, p, q, r);
		}
		return arr;
	}
	public static string[][] ReadMatrix<T>(int n, out T[][] a) {
		a = new T[n][];
		string[][] arr = new string[n][];
		for(int i=0; i<n; i++) arr[i] = ReadArray(out a[i]);
		return arr;
	}

	private static void wwp(Action act){
		var sw = new Printer(Console.OpenStandardOutput()){AutoFlush = false};
		Console.SetOut(sw);
		act();
		Console.Out.Flush();
		sw.AutoFlush = true;
		Console.SetOut(sw);
	}
	private static string wfm(Type t){
		return t.Equals(typeof(double)) ? "{0:F10}" : "{0}";
	}
	public static void WriteMulti(params object[] a) {
		wwp(()=>{
			for(int i=0;i<a.Length;i++) {
				string fs = wfm(a[i].GetType()) + ((i<a.Length-1) ? " " : Environment.NewLine);
				Console.Write(fs, a[i]);
			}
		});
	}
	public static void WriteArray<T>(T[] a) {
		wwp(()=>{
			string fs = wfm(typeof(T));
			string fs1 = fs + " ";
			string fs2 = fs + Environment.NewLine;
			for(int i=0;i<a.Length;i++) Console.Write( ((i<a.Length-1) ? fs1 : fs2), a[i]);
		});
	}
	public static void WriteLines<T>(T[] a) {
		wwp(()=>{
			string fs = wfm(typeof(T)) + Environment.NewLine;
			foreach(T x in a) Console.Write(fs, x);
		});
	}



}





Measured memory_bytes: 13792