/**
 * code generated by JHelper
 * More info: https://github.com/AlexeyDmitriev/JHelper
 * @author edamat
 */

#include <bits/stdc++.h>
//#if __has_include(<boost/version.hpp>)
//    #include <boost/operators.hpp>
//    #include <boost/integer/common_factor.hpp>
//    using namespace boost;
//#endif

using namespace std;

#define _overload3(_1, _2, _3, name, ...) name
#define _rep(i, n) repi(i, 0, n)
#define repi(i, a, b) for(int i = int(a), i##_len = (b); i < i##_len; ++i)
#define rep(...) _overload3(__VA_ARGS__, repi, _rep, )(__VA_ARGS__)
#define _repr(i, n) repri(i, n, 0)
/* loop in [n,m] step -1 */
#define repri(i, a, b) for(int i = int(a), i##_len = (b); i >= i##_len; --i)
/* loop in [n,0] step -1 or [n,m] step -1 */
#define repr(...) _overload3(__VA_ARGS__, repri, _repr, )(__VA_ARGS__)
#define all(x) (x).begin(), (x).end()
#define debug(x) cerr << #x << ": " << (x) << '\n'
#define pb push_back
#define append emplace_back
#define eb emplace_back
#define UNIQUE(v) v.erase(unique(v.begin(), v.end()), v.end());
#define SZ(x) ((int)(x).size())
//#define endl '\n'
typedef long long lint;
#define int lint
const int INF = numeric_limits<int>::max();
// int MOD = (int) 1e9 + 7;
// const double EPS = 1e-9;
const lint LINF = (lint)(1LL << 62) - 1;
typedef vector<int> vint;
typedef pair<int, int> pint;

struct IoSetup {
    IoSetup() {
        cin.tie(nullptr);
        ios::sync_with_stdio(false);
        //        cout << fixed << setprecision(10);
        //        cerr << fixed << setprecision(10);
    }
} iosetup;

using std::to_string;

auto to_string(std::string s) -> std::string { return s; }

auto to_string(char c) -> std::string { return std::string{c}; }

auto to_string(const char *s) -> std::string {
    return to_string((std::string)s);
}

auto to_string(bool b) -> std::string { return (b ? "true" : "false"); }

template <typename T, typename U>
auto to_string(std::pair<T, U> p) -> std::string {
    return "(" + to_string(p.first) + ", " + to_string(p.second) + ")";
}

template <size_t N>
auto to_string(std::bitset<N> bs) -> std::string {
    std::string res{};
    for(size_t i = 0; i < N; i++)
        res.insert(res.begin(), bs.test(i) ? '1' : '0');
    return res;
}

template <typename T>
auto to_string(T v) -> std::string {
    bool flg = false;
    std::string res = "[";
    for(auto const &x : v) {
        if(flg)
            res += ", ";
        else
            flg = true;
        res += to_string(x);
    }
    res += "]";
    return res;
}

/* keep a max,min */
template <class T>
bool chmax(T &a, const T &b) {
    if(a < b) {
        a = b;
        return 1;
    }
    return 0;
}

template <class T>
bool chmin(T &a, const T &b) {
    if(a > b) {
        a = b;
        return 1;
    }
    return 0;
}
/* gcd */
template <typename T>
T gcd(const T &a, const T &b) {
    if(b == 0)
        return a;
    else
        return gcd(b, a % b);
}

template <typename T>
T gcd(vector<T> &v) {
    T ret = v[0];
    for(auto x : v) {
        ret = gcd(ret, x);
    }
    return ret;
}

template <typename T, typename... A>
auto gcd(const T &first, const A &... rest) {
    return gcd(first, gcd(rest...));
}

/* initialize vector. usage: auto v = male_v<int>(N,0); */
template <typename T>
vector<T> make_v(size_t a) {
    return vector<T>(a);
}

template <typename T, typename... Ts>
auto make_v(size_t a, Ts... ts) {
    return vector<decltype(make_v<T>(ts...))>(a, make_v<T>(ts...));
}

/* fill vector. usage: fill_v(v,0); */
template <typename T, typename V>
typename std::enable_if<std::is_class<T>::value == 0>::type fill_v(T &t,
                                                                   const V &v) {
    t = v;
}

template <typename T, typename V>
typename std::enable_if<std::is_class<T>::value != 0>::type fill_v(T &t,
                                                                   const V &v) {
    for(auto &e : t) fill_v(e, v);
}

/* input,output operator for pair and vector */
template <typename T1, typename T2>
ostream &operator<<(ostream &os, const pair<T1, T2> &p) {
    os << p.first << " " << p.second;
    return os;
}

template <typename T1, typename T2>
istream &operator>>(istream &is, pair<T1, T2> &p) {
    is >> p.first >> p.second;
    return is;
}

template <typename T>
ostream &operator<<(ostream &os, const vector<T> &v) {
    for(int i = 0; i < (int)v.size(); i++) {
        os << v[i] << (i + 1 != v.size() ? " " : "");
    }
    return os;
}

template <typename T>
ostream &operator<<(ostream &os, const vector<vector<T>> &v) {
    for(int i = 0; i < (int)v.size(); i++) {
        os << v[i] << (i + 1 != v.size() ? "\n" : "");
    }
    return os;
}

template <typename T>
istream &operator>>(istream &is, vector<T> &v) {
    for(T &in : v) is >> in;
    return is;
}

template <typename T, typename U>
ostream &operator<<(ostream &os, const map<T, U> &mp) {
    os << "{";
    for(auto x = mp.begin(); x != mp.end(); ++x) {
        os << x->first << ":" << x->second << (x != prev(mp.end()) ? ", " : "");
    }
    os << "}";
    return os;
}

/* sum */
template <typename T>
lint sum(vector<T> &v) {
    lint ret = 0;
    for(auto x : v) {
        ret += x;
    }
    return ret;
}

double sum(vector<double> &v) {
    double ret = 0;
    for(auto x : v) {
        ret += x;
    }
    return ret;
}

template <typename T>
auto sum(const T &a) {
    return a;
}

template <typename T, typename... A>
auto sum(const T &first, const A &... rest) {
    return sum(first) + sum(rest...);
}

/* max */
template <typename T>
T max(vector<T> v) {
    T ret = numeric_limits<T>::min();
    for(auto x : v) {
        ret = max(ret, x);
    }
    return ret;
}

/* min */
template <typename T>
T min(vector<T> v) {
    T ret = numeric_limits<T>::max();
    for(auto x : v) {
        ret = min(ret, x);
    }
    return ret;
}

/* Modint */
template <typename T, T MOD = 1000000007>
// struct ModInt : private boost::operators<ModInt<T>> {
struct ModInt {
    static constexpr T mod = MOD;
    T v;
    ModInt() : v(0) {}
    ModInt(long long t) { v = t >= 0 ? t % mod : (mod - (-t) % mod) % mod; }

    ModInt pow(long long k) {
        ModInt res(1), tmp(v);
        while(k) {
            if(k & 1) res *= tmp;
            tmp *= tmp;
            k >>= 1;
        }
        return res;
    }

    static ModInt add_identity() { return ModInt(0); }
    static ModInt mul_identity() { return ModInt(1); }

    ModInt inv() { return pow(MOD - 2); }

    ModInt &operator+=(ModInt a) {
        v += a.v;
        if(v >= MOD) v -= MOD;
        return *this;
    }
    ModInt &operator-=(ModInt a) {
        v += MOD - a.v;
        if(v >= MOD) v -= MOD;
        return *this;
    }
    ModInt &operator*=(ModInt a) {
        v = 1LL * v * a.v % MOD;
        return *this;
    }
    ModInt &operator/=(ModInt a) { return (*this) *= a.inv(); }

    ModInt operator+(ModInt a) const { return ModInt(v) += a; };
    ModInt operator-(ModInt a) const { return ModInt(v) -= a; };
    ModInt operator*(ModInt a) const { return ModInt(v) *= a; };
    ModInt operator/(ModInt a) const { return ModInt(v) /= a; };

    ModInt operator-() const { return v ? ModInt(MOD - v) : ModInt(v); }

    bool operator==(const ModInt a) const { return v == a.v; }
    bool operator!=(const ModInt a) const { return v != a.v; }
    bool operator<(const ModInt a) const { return v < a.v; }

    // find x s.t. a^x = b
    static T log(T a, T b) {
        const T sq = 40000;
        unordered_map<T, T> dp;
        dp.reserve(sq);
        ModInt res(1);
        for(int r = 0; r < sq; r++) {
            if(!dp.count(res.v)) dp[res.v] = r;
            res *= a;
        }
        ModInt p = ModInt(a).inv().pow(sq);
        res = b;
        for(int q = 0; q <= MOD / sq + 1; q++) {
            if(dp.count(res.v)) {
                T idx = q * sq + dp[res.v];
                if(idx > 0) return idx;
            }
            res *= p;
        }
        assert(0);
        return T(-1);
    }

    static ModInt perm(long long n, int k) {
        ModInt num(1);
        for(int i = 0; i < k; i++) {
            num *= ModInt(n - i);
        }
        return num;
    }

    static ModInt comb(long long n, int k) {
        ModInt num(1), dom(1);
        for(int i = 0; i < k; i++) {
            num *= ModInt(n - i);
            dom *= ModInt(i + 1);
        }
        return num / dom;
    }
};
template <typename T, T MOD>
constexpr T ModInt<T, MOD>::mod;
template <typename T, T MOD>
ostream &operator<<(ostream &os, ModInt<T, MOD> m) {
    os << m.v;
    return os;
}

/* UnionFind */
struct UnionFind {
    int n, num;
    vector<int> r, p;

    UnionFind() {}

    UnionFind(int sz) : n(sz), num(sz), r(sz, 1), p(sz, 0) {
        iota(p.begin(), p.end(), 0);
    }

    inline int root(int x) { return (x == p[x] ? x : p[x] = root(p[x])); }

    inline int find(int x) { return (x == p[x] ? x : p[x] = find(p[x])); }

    inline bool same(int x, int y) { return root(x) == root(y); }

    bool unite(int x, int y) {
        x = root(x);
        y = root(y);
        if(x == y) return false;
        if(r[x] < r[y]) swap(x, y);
        r[x] += r[y];
        p[y] = x;
        num--;
        return true;
    }

    inline int size(int x) { return r[root(x)]; }

    inline int count() const { return num; }
};

struct QuickFind {
    vector<int> i2g;  // i2g[i] := アイテム i の所属するグループの番号
    vector<vector<int>> g2i;  // g2i[g] := グループ g に所属するアイテムたち

    QuickFind(int n) {
        i2g.resize(n);
        g2i.resize(n);
        for(int i = 0; i < n; ++i) {
            // 最初はアイテム i はグループ i に所属
            i2g[i] = i;
            g2i[i].assign(1, i);
        }
    }

    // アイテム ia の所属するグループとアイテム ib の所属するグループを 1
    // つにする （ia と ib は違うグループに所属するものとする）
    void unite(int ia, int ib) {
        // ia の所属するグループが ib
        // の所属するグループより小さくならないようにする
        if(g2i[i2g[ia]].size() < g2i[i2g[ib]].size()) {
            swap(ia, ib);
        }
        int ga = i2g[ia], gb = i2g[ib];
        // グループ gb に所属する全てのアイテムをグループ ga に移す
        for(int j : g2i[gb]) i2g[j] = ga;
        g2i[ga].insert(g2i[ga].end(), g2i[gb].begin(), g2i[gb].end());
        g2i[gb].clear();
    }

    // アイテム ia とアイテム ib は同じグループに所属しているか？
    bool same(int ia, int ib) { return i2g[ia] == i2g[ib]; }
};

template <typename T>
struct BIT {
    int n;
    vector<T> bit;
    // 1-indexed
    BIT() : n(-1) {}
    BIT(int n_, T d) : n(n_), bit(n_ + 1, d) {}

    T sum(int i) {
        T s = bit[0];
        for(int x = i; x > 0; x -= (x & -x)) s += bit[x];
        return s;
    }
    void add(int i, T a) {
        if(i == 0) return;
        for(int x = i; x <= n; x += (x & -x)) bit[x] += a;
    }

    int lower_bound(int w) {
        if(w <= 0) return 0;
        int x = 0, r = 1;
        while(r < n) r <<= 1;
        for(int k = r; k > 0; k >>= 1) {
            if(x + k <= n && bit[x + k] < w) {
                w -= bit[x + k];
                x += k;
            }
        }
        return x + 1;
    }

    T sum0(int i) { return sum(i + 1); }
    void add0(int i, T a) { add(i + 1, a); }

    T query(int l, int r) { return sum(r - 1) - sum(l - 1); }

    T query0(int l, int r) { return sum(r) - sum(l); }
};

/* SegmentTree */
template <typename T>
struct SegmentTree {
    using F = std::function<T(T, T)>;
    int n;
    F f;
    T ti;
    vector<T> dat;

    SegmentTree(){};

    SegmentTree(F f, T ti) : f(f), ti(ti) {}

    void init(int n_) {
        n = 1;
        while(n < n_) n <<= 1;
        dat.assign(n << 1, ti);
    }

    void build(const vector<T> &v) {
        int n_ = v.size();
        init(n_);
        for(int i = 0; i < n_; i++) dat[n + i] = v[i];
        for(int i = n - 1; i; i--)
            dat[i] = f(dat[(i << 1) | 0], dat[(i << 1) | 1]);
    }

    void update(int k, T x) {
        dat[k += n] = x;
        while(k >>= 1) dat[k] = f(dat[(k << 1) | 0], dat[(k << 1) | 1]);
    }

    T query(int a, int b) {
        T vl = ti, vr = ti;
        for(int l = a + n, r = b + n; l < r; l >>= 1, r >>= 1) {
            if(l & 1) vl = f(vl, dat[l++]);
            if(r & 1) vr = f(dat[--r], vr);
        }
        return f(vl, vr);
    }

    template <typename C>
    int find(int st, C &check, T &acc, int k, int l, int r) {
        if(l + 1 == r) {
            acc = f(acc, dat[k]);
            return check(acc) ? k - n : -1;
        }
        int m = (l + r) >> 1;
        if(m <= st) return find(st, check, acc, (k << 1) | 1, m, r);
        if(st <= l && !check(f(acc, dat[k]))) {
            acc = f(acc, dat[k]);
            return -1;
        }
        int vl = find(st, check, acc, (k << 1) | 0, l, m);
        if(~vl) return vl;
        return find(st, check, acc, (k << 1) | 1, m, r);
    }

    template <typename C>
    int find(int st, C &check) {
        T acc = ti;
        return find(st, check, acc, 1, 0, n);
    }

    T operator[](const int &k) const { return dat[k + n]; }
};

template <typename Monoid, typename OperatorMonoid = Monoid>
struct LazySegmentTree {
    using F = std::function<Monoid(Monoid, Monoid)>;
    using G = std::function<Monoid(Monoid, OperatorMonoid)>;
    using H = std::function<OperatorMonoid(OperatorMonoid, OperatorMonoid)>;
    using P = std::function<OperatorMonoid(OperatorMonoid, int)>;

    int sz;
    vector<Monoid> data;
    vector<OperatorMonoid> lazy;
    const F f;
    const G g;
    const H h;
    const P p;
    const Monoid M1;
    const OperatorMonoid OM0;

    LazySegmentTree(int n, const F f, const G g, const H h, const P p,
                    const Monoid &M1, const OperatorMonoid OM0)
        : f(f), g(g), h(h), p(p), M1(M1), OM0(OM0) {
        sz = 1;
        while(sz < n) sz <<= 1;
        data.assign(2 * sz, M1);
        lazy.assign(2 * sz, OM0);
    }

    void set(int k, const Monoid &x) { data[k + sz] = x; }

    void build() {
        for(int k = sz - 1; k > 0; k--) {
            data[k] = f(data[2 * k + 0], data[2 * k + 1]);
        }
    }

    void propagate(int k, int len) {
        if(lazy[k] != OM0) {
            if(k < sz) {
                lazy[2 * k + 0] = h(lazy[2 * k + 0], lazy[k]);
                lazy[2 * k + 1] = h(lazy[2 * k + 1], lazy[k]);
            }
            data[k] = g(data[k], p(lazy[k], len));
            lazy[k] = OM0;
        }
    }

    Monoid update(int a, int b, const OperatorMonoid &x, int k, int l, int r) {
        propagate(k, r - l);
        if(r <= a || b <= l) {
            return data[k];
        } else if(a <= l && r <= b) {
            lazy[k] = h(lazy[k], x);
            propagate(k, r - l);
            return data[k];
        } else {
            return data[k] = f(update(a, b, x, 2 * k + 0, l, (l + r) >> 1),
                               update(a, b, x, 2 * k + 1, (l + r) >> 1, r));
        }
    }

    Monoid update(int a, int b, const OperatorMonoid &x) {
        return update(a, b, x, 1, 0, sz);
    }

    Monoid query(int a, int b, int k, int l, int r) {
        propagate(k, r - l);
        if(r <= a || b <= l) {
            return M1;
        } else if(a <= l && r <= b) {
            return data[k];
        } else {
            return f(query(a, b, 2 * k + 0, l, (l + r) >> 1),
                     query(a, b, 2 * k + 1, (l + r) >> 1, r));
        }
    }

    Monoid query(int a, int b) { return query(a, b, 1, 0, sz); }

    Monoid operator[](const int &k) { return query(k, k + 1); }
};

template <typename Monoid, typename OperatorMonoid = Monoid>
struct AddMinLazySegmentTree : LazySegmentTree<Monoid> {
    AddMinLazySegmentTree(int n)
        : LazySegmentTree<Monoid>(n, [](int a, int b) { return min(a, b); },
                                  [](int a, int b) { return a + b; },
                                  [](int a, int b) { return a + b; },
                                  [](int a, int b) { return a; },
                                  numeric_limits<Monoid>::max(), 0) {}
    AddMinLazySegmentTree(vector<Monoid> &v)
        : LazySegmentTree<int>(SZ(v), [](int a, int b) { return min(a, b); },
                               [](int a, int b) { return a + b; },
                               [](int a, int b) { return a + b; },
                               [](int a, int b) { return a; },
                               numeric_limits<Monoid>::max(), 0) {
        for(int i = 0; i < SZ(v); ++i) this->set(i, v[i]);
        this->build();
    }
};

template <typename Monoid = long long, typename OperatorMonoid = Monoid>
struct AddSumLazySegmentTree : LazySegmentTree<Monoid> {
    AddSumLazySegmentTree(int n)
        : LazySegmentTree<Monoid>(
              n, [](Monoid a, Monoid b) { return a + b; },
              [](Monoid a, OperatorMonoid b) { return a + b; },
              [](OperatorMonoid a, OperatorMonoid b) { return a + b; },
              [](OperatorMonoid a, int b) { return a * b; }, 0, 0) {}
    AddSumLazySegmentTree(vector<Monoid> v)
        : LazySegmentTree<Monoid>(
              SZ(v), [](Monoid a, Monoid b) { return a + b; },
              [](Monoid a, OperatorMonoid b) { return a + b; },
              [](OperatorMonoid a, OperatorMonoid b) { return a + b; },
              [](OperatorMonoid a, int b) { return a * b; }, 0, 0) {
        for(int i = 0; i < SZ(v); ++i) this->set(i, v[i]);
        this->build();
    }
};

template <typename Monoid, typename OperatorMonoid = Monoid>
struct UpdateMinLazySegmentTree : LazySegmentTree<Monoid> {
    UpdateMinLazySegmentTree(int n)
        : LazySegmentTree<Monoid>(
              n, [](Monoid a, Monoid b) { return min(a, b); },
              [](Monoid a, OperatorMonoid b) { return b == 0 ? a : b; },
              [](OperatorMonoid a, OperatorMonoid b) { return b == 0 ? a : b; },
              [](OperatorMonoid a, int b) { return a; },
              numeric_limits<Monoid>::max(), 0) {}
    UpdateMinLazySegmentTree(vector<Monoid> v)
        : LazySegmentTree<Monoid>(
              SZ(v), [](Monoid a, Monoid b) { return min(a, b); },
              [](Monoid a, OperatorMonoid b) {
                  return b == numeric_limits<OperatorMonoid>::max() ? a : b;
              },
              [](OperatorMonoid a, OperatorMonoid b) {
                  return b == numeric_limits<OperatorMonoid>::max() ? a : b;
              },
              [](OperatorMonoid a, int b) { return a; },
              numeric_limits<Monoid>::max(),
              numeric_limits<OperatorMonoid>::max()) {
        for(int i = 0; i < SZ(v); ++i) this->set(i, v[i]);
        this->build();
    }
};

template <typename Monoid, typename OperatorMonoid = Monoid>
struct UpdateSumLazySegmentTree : LazySegmentTree<Monoid> {
    UpdateSumLazySegmentTree(int n)
        : LazySegmentTree<Monoid>(
              n, [](Monoid a, Monoid b) { return a + b; },
              [](Monoid a, OperatorMonoid b) { return b == 0 ? a : b; },
              [](OperatorMonoid a, OperatorMonoid b) { return b == 0 ? a : b; },
              [](OperatorMonoid a, int b) { return a * b; },
              numeric_limits<Monoid>::max(), 0) {}
    UpdateSumLazySegmentTree(vector<Monoid> v)
        : LazySegmentTree<Monoid>(
              SZ(v), [](Monoid a, Monoid b) { return a + b; },
              [](Monoid a, OperatorMonoid b) {
                  return b == numeric_limits<OperatorMonoid>::max() ? a : b;
              },
              [](OperatorMonoid a, OperatorMonoid b) {
                  return b == numeric_limits<OperatorMonoid>::max() ? a : b;
              },
              [](OperatorMonoid a, int b) { return a * b; }, 0,
              numeric_limits<OperatorMonoid>::max()) {
        for(int i = 0; i < SZ(v); ++i) this->set(i, v[i]);
        this->build();
    }
};
template <class T>
struct CumulativeSum {
    vector<T> data;

    CumulativeSum(){};
    CumulativeSum(int sz) : data(sz, 0){};

    void add(int k, T x) { data[k] += x; }

    void build() {
        for(int i = 1; i < data.size(); i++) {
            data[i] += data[i - 1];
        }
    }

    void build(vector<T> v) {
        data = v;
        for(int i = 1; i < data.size(); i++) {
            data[i] += data[i - 1];
        }
    }

    T query(int k) {
        if(k < 0) return (0);
        return (data[min(k, (int)data.size() - 1)]);
    }

    T query(int l, int r) {
        if(l < 0 || r > data.size()) return (0);
        return query(r - 1) - query(l - 1);
    }
};

struct RandomNumberGenerator {
    mt19937 mt;

    RandomNumberGenerator()
        : mt(chrono::steady_clock::now().time_since_epoch().count()) {}

    int operator()(int a, int b) {  // [a, b)
        uniform_int_distribution<int> dist(a, b - 1);
        return dist(mt);
    }

    int operator()(int b) {  // [0, b)
        return (*this)(0, b);
    }
};

struct ELeague {
    static constexpr int kStressIterations = 0;

    static void generateTest(int cnt, std::ostream &test) {
        RandomNumberGenerator rng;
    }

    void solve(std::istream &cin, std::ostream &cout) {
        int N;
        cin >> N;
        auto A = make_v<int>(N, N - 1);
        rep(i, N) cin >> A[i];
        vector<int> now(N, 0), nowday(N, 0);
        queue<int> q;
        rep(i, N) q.push(i);
        while(!q.empty()) {
            auto p = q.front();
            q.pop();
            if(now[p] == N - 1) continue;
            if(nowday[p] > N * (N - 1) / 2) break;
            int a = A[p][now[p]] - 1;
            //            cout << p  <<" " << a << endl;
            if(A[a][now[a]] == p + 1) {
                now[a]++;
                now[p]++;
                int M = max(nowday[a], nowday[p]);
                if(now[p] != N - 1) q.push(p);
                if(now[a] != N - 1) q.push(a);
                nowday[a] = M + 1;
                nowday[p] = M + 1;
            }
        }
        // cout << nowday << endl;
        vector<int> mohan(N, N - 1);
        if(now == mohan)
            cout << max(nowday) << endl;
        else
            cout << -1 << endl;
    }
};

signed main() {
    ELeague solver;
    std::istream &in(std::cin);
    std::ostream &out(std::cout);
    solver.solve(in, out);
    return 0;
}


Measured memory_bytes: 8192