use std::collections::BTreeMap;

const MOD_ARRAY: [u64; 3] = [998244353, 1_000_000_007, 1_000_000_009];

fn main() {
    let s = std::io::stdin();
    let mut sc = Scanner { stdin: s.lock() };
    let n: usize = sc.read();
    let s = sc.read::<String>();

    let rolling_hash0 = RollingHash::new(s.as_bytes(), MOD_ARRAY[0]);
    let rolling_hash1 = RollingHash::new(s.as_bytes(), MOD_ARRAY[1]);
    let rolling_hash2 = RollingHash::new(s.as_bytes(), MOD_ARRAY[2]);
    let mut ok = 0;
    let mut ng = n;
    while ng - ok > 1 {
        let length = (ng + ok) / 2;
        let mut hash_map = BTreeMap::new();
        for i in 0..(n - length + 1) {
            let hash0 = rolling_hash0.get_hash(i, i + length);
            let hash1 = rolling_hash1.get_hash(i, i + length);
            let hash2 = rolling_hash2.get_hash(i, i + length);
            hash_map
                .entry((hash0, hash1, hash2))
                .or_insert(Vec::new())
                .push(i);
        }

        //        eprintln!("length={} hash_map={:?}", length, hash_map.values());
        let mut contains = false;
        for (_, starts) in hash_map.into_iter() {
            let n = starts.len();
            for i in 0..n {
                let start1 = starts[i];
                let lower_start = start1 + length;
                let has_next = match starts.binary_search(&lower_start) {
                    Ok(_) => true,
                    Err(pos) => pos < n,
                };
                if has_next {
                    //                    eprintln!(
                    //                        "Found! length={} start1={} lower_start={} starts={:?}",
                    //                        length, start1, lower_start, starts
                    //                    );
                    contains = true;
                }
                if contains {
                    break;
                }
            }
            if contains {
                break;
            }
        }

        if contains {
            ok = length;
        } else {
            ng = length;
        }
    }

    println!("{}", ok);
}

pub struct RollingHash {
    hash: Vec<u64>,
    pow: Vec<u64>,
}

impl RollingHash {
    pub fn new(s: &[u8], base: u64) -> RollingHash {
        let n = s.len();
        let mut hash: Vec<u64> = vec![0; n + 1];
        let mut pow: Vec<u64> = vec![0; n + 1];
        pow[0] = 1;
        for i in 0..n {
            pow[i + 1] = pow[i].wrapping_mul(base);
            hash[i + 1] = hash[i].wrapping_mul(base).wrapping_add(s[i] as u64);
        }
        RollingHash {
            hash: hash,
            pow: pow,
        }
    }

    /// Get hash of [l, r)
    pub fn get_hash(&self, l: usize, r: usize) -> u64 {
        self.hash[r].wrapping_sub(self.hash[l].wrapping_mul(self.pow[r - l]))
    }
}
pub struct Scanner<R> {
    stdin: R,
}

impl<R: std::io::Read> Scanner<R> {
    pub fn read<T: std::str::FromStr>(&mut self) -> T {
        use std::io::Read;
        let buf = self
            .stdin
            .by_ref()
            .bytes()
            .map(|b| b.unwrap())
            .skip_while(|&b| b == b' ' || b == b'\n')
            .take_while(|&b| b != b' ' && b != b'\n')
            .collect::<Vec<_>>();
        unsafe { std::str::from_utf8_unchecked(&buf) }
            .parse()
            .ok()
            .expect("Parse error.")
    }
    pub fn vec<T: std::str::FromStr>(&mut self, n: usize) -> Vec<T> {
        (0..n).map(|_| self.read()).collect()
    }
    pub fn chars(&mut self) -> Vec<char> {
        self.read::<String>().chars().collect()
    }
}


Measured memory_bytes: 4352