#include <bits/stdc++.h>

using ll = long long;
using namespace std;

#define swap(a, b)    do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while(false)
#define repd(i, a, b) for(typeof(b) i = a; i < (b); ++i)
#define rep(i, n)     repd(i, 0, n)
#define dump(x)       cerr << #x << " = " << (x) << endl
#define debug(x)      cerr << "L" << __LINE__ << ": " << #x << " = " << (x) << endl
#define YN(x)         cout << ((x) ? "YES" : "NO") << endl
#define Yn(x)         cout << ((x) ? "Yes" : "No") << endl
#define yn(x)         cout << ((x) ? "yes" : "no") << endl
#define absi(x)       ( ((x) >= 0) ? (x) : (-(x)) )
#define debugvec(v)   rep(i, v.size()) cerr << "L" << __LINE__ << ": " << #v << "[" << i << "] = " << v[i] << endl;
#define debugvec2(v)  cerr << "L" << __LINE__ << ": " << #v << " = { "; rep(i, v.size()) cerr << v[i] << (i + 1 == v.size() ? "" : ", "); cerr << " }" << endl;

ll gcd(ll a, ll b) { return __gcd(a, b); }
ll lcm(ll a, ll b) { return (a == 0 || b == 0) ? 0 : (a / __gcd(a, b) * b); }

class directedgraph {
private:
	std::vector<std::vector<std::size_t>> al; // Adjacency List
	std::vector<std::vector<std::size_t>> ar; // Adjacency List Reverse
	const std::size_t n;
	static constexpr std::size_t infinity = std::numeric_limits<std::size_t>::max();
public:
	directedgraph(const std::size_t n) : n(n) {
		al = std::vector<std::vector<std::size_t>>(n);
		ar = std::vector<std::vector<std::size_t>>(n);
	}
	
	bool add_edge(const std::size_t tail, const std::size_t head, const bool check_multiple_edges = false) {
		if( !check_multiple_edges ) {
			al.at(tail).push_back(head);
			ar.at(head).push_back(tail);
			return true;
		}

		if(has_edge(tail, head)) {
			return false;
		}
		al.at(tail).push_back(head);
		ar.at(head).push_back(tail);
		return true;
	}

	/* tail -> head の時、tail からみた head */
	std::vector<std::size_t>& direct_successors_of(const std::size_t vertex) {
		return al.at(vertex);
	}

	const std::vector<std::size_t>& direct_successors_of(const std::size_t vertex) const {
		return al.at(vertex);
	}

	/* tail -> head の時、head からみた tail */
	std::vector<std::size_t>& direct_predecessors_of(const std::size_t vertex) {
		return ar.at(vertex);
	}

	const std::vector<std::size_t>& direct_predecessors_of(const std::size_t vertex) const {
		return ar.at(vertex);
	}

	/* 入次数 0 の頂点 */
	std::vector<std::size_t> sources() const {
		std::vector<std::size_t> ret(0);
		for(std::size_t i = 0; i < al.size(); ++i) {
			if(indegree(i) == 0) {
				ret.push_back(i);
			}
		}
		return ret;
	}

	/* 出次数 0 の頂点 */
	std::vector<std::size_t> sinks() const {
		std::vector<std::size_t> ret(0);
		for(std::size_t i = 0; i < al.size(); ++i) {
			if(outdegree(i) == 0) {
				ret.push_back(i);
			}
		}
		return ret;
	}

	bool has_edge(const std::size_t tail, const std::size_t head) const {
		return std::find(direct_successors_of(tail).begin(), direct_successors_of(tail).end(), head) 
		       != direct_successors_of(tail).end();
	}

	bool has_path(const std::size_t from, const std::size_t to) const {
		return distance(from, to) != infinity;
	}

	bool is_dag() const {
		return is_acyclic();
	}

	bool is_acyclic() const {
		const auto v = topological_sort();
		return !v.empty();
	}

	bool is_cyclic() const {
		return !is_acyclic();
	}

	std::size_t indegree(const std::size_t vertex) const {
		return direct_predecessors_of(vertex).size();
	}

	std::size_t outdegree(const std::size_t vertex) const {
		return direct_successors_of(vertex).size();
	}

	std::size_t distance(const std::size_t from, const std::size_t to) const {
		std::size_t shortest_path_size = shortest_path(from, to).size();
		return (shortest_path_size == 0) ? infinity : (shortest_path_size - 1);
	}

	/* 最短経路を BFS(幅優先探索) で探索 */
	std::vector<std::size_t> shortest_path(const std::size_t first_vertex, const std::size_t last_vertex) const {
		if(first_vertex == last_vertex) {
			return std::vector<std::size_t>(1, first_vertex);
		}

		std::vector<std::size_t> ds(al.size(), infinity); // ds[i] = i から次行くべき vertex

		std::queue<std::size_t> q;
		q.push(last_vertex);

		while(!q.empty()) {
			std::size_t current = q.front(); q.pop();

			auto dp = direct_predecessors_of(current);
			for(const auto e : dp) {
				if(ds.at(e) == infinity) {
					ds.at(e) = current;
				}

				if(e == first_vertex) {
					std::vector<std::size_t> ret(1, first_vertex);
					std::size_t current_vertex = first_vertex;
					do {
						current_vertex = ds.at(current_vertex);
						ret.push_back(current_vertex);
					} while(current_vertex != last_vertex);

					return ret;
				}
			}
		}

		return std::vector<std::size_t>(0);
	}

	/*
	 *  多分バグっていない
	 *  これは通った：https://atcoder.jp/contests/nikkei2019-qual/tasks/nikkei2019_qual_d
	 */
	std::vector<std::size_t> topological_sort() const {
		std::vector<std::size_t> indegrees(al.size(), 0); // indegree[i] = i の入次数。変更するため配列に格納
		for(std::size_t i = 0; i < al.size(); ++i) {
			indegrees.at(i) = indegree(i);
		}

		queue<std::size_t> q;
		for(const auto& source : sources()) {
			q.push(source);
		}

		/* Edge が刺さっていない Vertex が存在しなければ、Cyclic */
		if( q.empty() ) {
			return std::vector<std::size_t>(0);
		}

		/* BFS */
		std::vector<std::size_t> ret(0); ret.reserve( al.size() );
		while( !q.empty() ) {
			std::size_t current = q.front(); q.pop();
			ret.push_back(current);
			for( const auto& current_to : direct_successors_of(current) ) {
				indegrees.at(current_to) -= 1;
				if(indegrees.at(current_to) == 0) {
					q.push(current_to);
				}
			}
		}

		/* Edge が残っていれば、Cyclic */
		if( *max_element(indegrees.begin(), indegrees.end()) > 0 ) {
			return std::vector<std::size_t>(0);
		}

		return ret;
	}

	/*
	 * 多分バグっていない
	 * これは通った：https://atcoder.jp/contests/abc139/tasks/abc139_e
	 */
	std::vector<std::vector<std::size_t>> topological_sort_extended() const {
		auto ts = topological_sort();
		if(ts.empty()) return std::vector<std::vector<std::size_t>>(0);

		std::vector<std::size_t> order(al.size(), 0);
		for(const auto& i : ts) {
			for( const auto& j : direct_successors_of(i) ) {
				order.at(j) = std::max(order.at(i) + 1, order.at(j));
			}
		}

		std::vector<std::vector<std::size_t>> ret( order.at(ts.back()) + 1 );
		for(std::size_t i = 0; i < ts.size(); ++i) {
			ret.at(order.at(i)).push_back(i);
		}

		return ret;
	}
};

ll game_number(ll a, ll b, ll n) {
	if(a > b) swap(a, b);
	return a * n - a * (a + 1) / 2 + b - a - 1;
}

int main(int argc, const char* argv[]) {
	ll n;
	cin >> n;
	vector<vector<ll>> a(n, vector<ll>(n - 1, 0));
	directedgraph g( (n * n - n) / 2 );

	rep(i, n) {
		rep(j, n - 1) {
			cin >> a[i][j];
			--a[i][j];
		}
	}

	vector<vector<ll>> b(n, vector<ll>(n, 0)); // 試合をノードの番号にしたやつ
	rep(i, n) {
		rep(j, n - 1) {
			ll player1 = i;
			ll player2 = a[i][j];
			b[i][j] = game_number(player1, player2, n);
		}
	}

	rep(i, n) {
		rep(j, n - 2) {
			g.add_edge(b[i][j], b[i][j+1]);
		}
	}

	auto ts = g.topological_sort_extended();
	ll ret = ts.size();
	if(ret == 0) ret = -1;

	for(const auto& x: ts) {
		for(const auto& y: x) {
			cerr << y << " ";
		}
		cerr << endl;
	}

	cout << ret << endl;

	return 0;
}


Measured memory_bytes: 105664