#include<iostream>
#include<iomanip>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<vector>
#include<array>
#include<string>
#include<stack>
#include<queue>
#include<algorithm>
#include <iterator>
#include<cassert>
#include<functional>
#include<random>
#include<complex>
#include<bitset>
#include<cstdint>
#include<chrono>
#define int int64_t
#define uint uint64_t
#define REP(i, a, b) for (int64_t i = (int64_t)(a); i < (int64_t)(b); i++)
#define rep(i, a) REP(i, 0, a)
#define EACH(i, a) for (auto i: a)
#define ITR(x, a) for (auto x = a.begin(); x != a.end(); x++)
#define ALL(a) (a.begin()), (a.end())
#define HAS(a, x) (a.find(x) != a.end())
#define Min(x) *min_element(ALL(x))
#define Max(x) *max_element(ALL(x))
#define Unique(L) (L.erase(unique(ALL(L)), L.end()))
#define veccat(v1, v2) std::copy((v2).begin(),(v2).end(),std::back_inserter(v1)/*v1の後ろにv2を入れる*/)
#define intmax (std::numeric_limits<int64_t>::max() / 4)
using namespace std;
//typedef boost::multiprecision::cpp_int bigint;
const double EPS = 1e-9;
const double PI = acos(-1.0);

using namespace std;

class SuffixArrayMaker {

private:

	bool TGet(const std::vector<uint8_t>&t, const int index) {
		return (t[index / 8] & (uint8_t(1) << (index % 8))) ? true : false;
	}
	void TSet(std::vector<uint8_t>&t, const int index, const bool value) {
		t[index / 8] = value ?
			(t[index / 8] | (uint8_t(1) << (index % 8))) :
			(t[index / 8] & (~(uint8_t(1) << (index % 8))));
	}
	bool IsLMS(const std::vector<uint8_t>&t, const int index) {
		return (index > 0) && TGet(t, index) && (!TGet(t, index - 1));
	}
	template<typename T>void GetBuckets(
		const T* input,
		std::vector<int>&bucket,
		const int n,
		const int K,
		const bool end) {
		for (int i = 0; i <= K; ++i)bucket[i] = 0;
		for (int i = 0; i < n; ++i)++bucket[input[i]];
		int sum = 0;
		for (int i = 0; i <= K; ++i) {
			sum += bucket[i];
			bucket[i] = end ? sum : (sum - bucket[i]);
		}
	}
	template<typename T>void InducedSortArrayL(
		const T* input,
		const std::vector<uint8_t>&t,
		int* suffix_array,
		std::vector<int>&bucket,
		const int n,
		const int K,
		const bool end) {
		GetBuckets<T>(input, bucket, n, K, end);
		for (int i = 0; i < n; ++i) {
			const int j = suffix_array[i] - 1;
			if ((j >= 0) && (!TGet(t, j)))suffix_array[bucket[input[j]]++] = j;
		}
	}
	template<typename T>void InducedSortArrayS(
		const T* input,
		const std::vector<uint8_t>&t,
		int* suffix_array,
		std::vector<int>&bucket,
		const int n,
		const int K,
		const bool end) {
		GetBuckets<T>(input, bucket, n, K, end);
		for (int i = n - 1; i >= 0; --i) {
			const int j = suffix_array[i] - 1;
			if ((j >= 0) && TGet(t, j))suffix_array[--bucket[input[j]]] = j;
		}
	}
	template<typename T>void SA_IS(const T* input, int* suffix_array, const int n, const int K) {

		std::vector<uint8_t>t((n + 7) / 8, 0);
		TSet(t, n - 2, false);
		TSet(t, n - 1, true);
		for (int i = n - 3; i >= 0; --i) {
			bool sets = (input[i] < input[i + 1]) ||
				(input[i] == input[i + 1] && TGet(t, i + 1));
			TSet(t, i,
				(input[i] < input[i + 1]) ||
				(input[i] == input[i + 1] && TGet(t, i + 1))
			);
		}

		//stage 1
		std::vector<int>bucket(K + 1, 0);
		GetBuckets<T>(input, bucket, n, K, true);
		for (int i = 0; i < n; ++i)suffix_array[i] = -1;
		for (int i = 1; i < n; ++i)if (IsLMS(t, i))suffix_array[--bucket[input[i]]] = i;
		InducedSortArrayL<T>(input, t, suffix_array, bucket, n, K, false);
		InducedSortArrayS<T>(input, t, suffix_array, bucket, n, K, true);
		int n1 = 0;
		for (int i = 0; i < n; ++i) {
			if (IsLMS(t, suffix_array[i]))suffix_array[n1++] = suffix_array[i];
		}
		for (int i = n1; i < n; ++i)suffix_array[i] = -1;
		int name = 0, prev = -1;
		for (int i = 0; i < n1; ++i) {
			int pos = suffix_array[i];
			bool diff = false;
			for (int d = 0; d < n; ++d) {
				if ((prev == -1) ||
					(input[pos + d] != input[prev + d]) ||
					(TGet(t, pos + d) != TGet(t, prev + d))) {
					diff = true;
					break;
				}
				else if (d > 0 && (IsLMS(t, pos + d) || IsLMS(t, prev + d)))break;
			}
			if (diff) {
				name++;
				prev = pos;
			}
			pos = ((pos % 2) == 0) ? (pos / 2) : ((pos - 1) / 2);
			suffix_array[n1 + pos] = name - 1;
		}
		for (int i = n - 1, j = n - 1; i >= n1; --i) {
			if (suffix_array[i] >= 0)suffix_array[j--] = suffix_array[i];
		}

		//stage 2
		int* suffix_array1 = suffix_array;
		int* s1 = suffix_array + n - n1;
		if (name < n1) {
			SA_IS<int>(s1, suffix_array1, n1, name - 1);
		}
		else {
			for (int i = 0; i < n1; ++i)suffix_array1[s1[i]] = i;
		}

		//stage 3
		GetBuckets<T>(input, bucket, n, K, true);
		for (int i = 1, j = 0; i < n; ++i) {
			if (IsLMS(t, i))s1[j++] = i;
		}
		for (int i = 0; i < n1; ++i)suffix_array1[i] = s1[suffix_array1[i]];
		for (int i = n1; i < n; ++i)suffix_array[i] = -1;
		for (int i = n1 - 1; i >= 0; --i) {
			const int j = suffix_array[i];
			suffix_array[i] = -1;
			suffix_array[--bucket[input[j]]] = j;
		}
		InducedSortArrayL<T>(input, t, suffix_array, bucket, n, K, false);
		InducedSortArrayS<T>(input, t, suffix_array, bucket, n, K, true);
	}

public:

	std::vector<int> SuffixArrayConstruction(const std::vector<uint8_t>& input) {

		for (int i = 0; i < input.size() - 1; ++i)assert(input[i] != 0);
		assert(input.back() == 0);

		std::vector<int>suffix_array(input.size(), 0);
		SA_IS<uint8_t>(&input[0], &suffix_array[0], input.size(), 256);
		return suffix_array;
	}

	std::vector<int> SuffixArrayConstruction(const std::string& input) {
		std::vector<uint8_t>tmp;
		for (char c : input)tmp.push_back(uint8_t(c));
		tmp.push_back(0);
		return SuffixArrayConstruction(tmp);
	}

	//LCP arrayとは、suffix array上で隣接する接尾辞の共通する接頭辞の文字数を格納した配列で、長さはN-1である。
	//Kasaiの方法でO(N)で計算する
	//Suffix Arrayと密接に関係しているのでこのクラスの中に入れた。
	std::vector<int>KasaiLCPArrayConstruction(
		const std::vector<uint8_t>& input,
		const std::vector<int>& suffix_array) {

		for (int i = 0; i < input.size() - 1; ++i)assert(input[i] != 0);
		assert(input.back() == 0);
		assert(input.size() == suffix_array.size());

		std::vector<int>inverse_suffix_array(suffix_array.size(), 0);
		for (int i = 0; i < inverse_suffix_array.size(); ++i) {
			inverse_suffix_array[suffix_array[i]] = i;
		}

		std::vector<int>lcp_array(input.size() - 1, 0);
		int lcp = 0;
		for (int i = 0; i < input.size(); ++i) {
			if (inverse_suffix_array[i] == input.size() - 1) {
				lcp = 0;
				continue;
			}
			const int pos1 = suffix_array[inverse_suffix_array[i]];
			const int pos2 = suffix_array[inverse_suffix_array[i] + 1];
			while (input[pos1 + lcp] == input[pos2 + lcp])lcp++;
			lcp_array[inverse_suffix_array[i]] = lcp;
			if (lcp > 0)--lcp;
		}
		return lcp_array;
	}

	std::vector<int>KasaiLCPArrayConstruction(
		const std::string& input,
		const std::vector<int>& suffix_array) {

		std::vector<uint8_t>tmp;
		for (char c : input)tmp.push_back(uint8_t(c));
		tmp.push_back(0);
		return KasaiLCPArrayConstruction(tmp, suffix_array);
	}
};


signed main() {


	int N;
	string S;
	cin >> N >> S;

	SuffixArrayMaker sa;
	vector<int>a = sa.SuffixArrayConstruction(S);
	vector<int>l = sa.KasaiLCPArrayConstruction(S, a);

	int lb = -1, ub = N;
	while (lb + 1 < ub) {
		int mid = (lb + ub) / 2;

		bool flag = false;
		int low = -1, high = -1;
		for (int i = 0; i < N; ++i) {
			if (l[i] >= mid) {
				if (low == -1) {
					low = a[i] < a[i + 1] ? a[i] : a[i + 1];
					high = a[i] < a[i + 1] ? a[i + 1] : a[i];
				}
				else {
					low = low < a[i + 1] ? low : a[i + 1];
					high = high < a[i + 1] ? a[i + 1] : high;
				}
				if (high - low >= mid) {
					flag = true;
					break;
				}
			}
			else {
				low = -1;
				high = -1;
			}
		}

		if (flag) {
			lb = mid;
		}
		else {
			ub = mid;
		}
	}

	cout << lb << endl;



	return 0;
}

Measured memory_bytes: 384