/* ＵＴＦ－８ */
#include <iostream>
#include <string>
#include <vector>
#include <deque>
#include <list>
#include <set>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <array>
#include <queue>
#include <stack>
#include <algorithm>
#include <tuple>
#include <numeric>
#include <type_traits>
#include <limits>

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#define USE_COLORED 1

#ifdef USE_COLORED
static const char stdout_pre[]  = "\033[1m"; // bold black
static const char stderr_pre[]  = "\033[31;1m"; // bold red
static const char stdxxx_post[] = "\033[m";

// 当然ながら GCC(g++)/libstdc++ 依存
#include <ext/stdio_filebuf.h>
using stdio_filebuf = __gnu_cxx::stdio_filebuf<char>;
class colored_stdio_filebuf : public stdio_filebuf {
private:
	const char* const pre_;
	const char* const post_;

public:
	colored_stdio_filebuf(FILE* fp, const char* pre, const char* post) : stdio_filebuf(fp, std::ios_base::out), pre_(pre), post_(post)
	{
	}

protected:
	std::streamsize xsputn(const char* s, std::streamsize count) override
	{
		stdio_filebuf::xsputn(pre_, strlen(pre_));
		auto ret = stdio_filebuf::xsputn(s, count);
		stdio_filebuf::xsputn(post_, strlen(post_));
		return ret;
	}
};
#endif

std::vector<int> z_algorithm(const char* s, size_t len)
{
	std::vector<int> prefix(len);
	// i : 先頭から始まる文字列と i 文字目から始まる文字列の一致長を計算する
	// j : 最後に明示的に一致長を計算した index。これは結果的に j < i で、かつ 最も大きい j + p[j] を与える j となる。
	// ちなみに j から始まって p[j] 文字の範囲を Z-box と呼ぶ
	for (int i = 1, j = 0; i < len; i++) {
		// j 文字目からは p[j] 文字一致しているのだが、その一致している範囲に i がある（つまり j + prefix[j] > i <=> ）として、
		// i から始まる k = j + prefix[j] - i 文字は i-j 文字目から始まる文字列と同じになっているはずである。
		if(i + prefix[i - j] < j + prefix[j]) {
			// p[i-j] が k より小さいのであれば、もう文字の比較をするまでもなく、 p[i] は p[i-j] と同じになる。
			prefix[i] = prefix[i - j];
		} else {
			// そうでない場合は、普通に比較をしていく
			// 単純に先頭から比較すると N^2 になってしまうが、最初の k 文字は一致していることはわかっているので、その分スキップする
			// j + p[j] - i はアルゴリズムの最初のうちはマイナスなので、その場合は 0 からスタートする
			int k = std::max(0, j + prefix[j] - i);
			// 先頭から始まる文字列の k 文字目以降と、 i 文字目から始まる文字列の k 文字目以降の一致を見る
			while (i + k < len && s[k] == s[i + k])
				++k;
			// ↑ここの文字の比較において、
			// mismatch の回数は while1回に付き高々1回なので、外の for を勘案すると高々 N 回となる
			// 1回matchするたびに Z-box が後ろに伸びていき、Z-box 内では同じ match は行わないので、これまた高々 N 回となる
			// そういうわけで、Z algorithm 全体として O(N)-time となる。 O(N)-space なのは見ればわかる。
			prefix[i] = k;
			j = i;
		}
	}
	prefix[0] = (int)len; // これは最後にやらないと正しい答えにならない
	return prefix;
}

std::vector<int> z_algorithm(const char* s)
{
	return z_algorithm(s, strlen(s));
}

std::vector<int> z_algorithm(const std::string& s)
{
	return z_algorithm(s.c_str(), s.size());
}


int main()
{
#ifdef USE_COLORED
	if (getenv("DEBUG")) {
		std::cout.rdbuf(new colored_stdio_filebuf(stdout, stdout_pre, stdxxx_post));
		std::cerr.rdbuf(new colored_stdio_filebuf(stderr, stderr_pre, stdxxx_post));
	} else
#endif
	{
		int fd = open("/dev/null", O_WRONLY);
		dup2(fd, 2);
		close(fd);
	}

	// ABC141E

	int N;
	std::string S;
	std::cin >> N;
	std::cin >> S;

	int result = 0;
	for (int i = 0; i < S.size()-1; ++i) {
		auto matchlen = z_algorithm(S.c_str() + i, S.size() - i);
		for (int j = 1; j < matchlen.size(); ++j) {
			if (matchlen[j] <= j)
				result = std::max(result, matchlen[j]);
		}
	}
	
	std::cout << result << "\n";
}


Measured memory_bytes: 512