#include <bits/stdc++.h>

struct Edge {
	int to;
};
using EdgeVec = std::vector<Edge>;
using EdgeLists = std::vector<EdgeVec>;

///////////////////
// 強連結成分分解 //
///////////////////

// コンストラクタに隣接リストを渡すと強連結成分分解する
// Edgeは{to}
class SCC {
private:
	using vi = std::vector<int>;
	using vvi = std::vector<vi>;
	using vb = std::vector<bool>;

	const EdgeLists& edge_;
	vvi componentElements_, revEdge_;
	vi belongTo_;

	void backTrack(const int index, vi& revOrder, vb& visited)
	{
		if (visited[index]) return;
		visited[index] = true;
		for (const auto& next: edge_[index])
			backTrack(next.to, revOrder, visited);
		revOrder.push_back(index);
	}
	void assignComponent(const int index)
	{
		if (belongTo_[index] >= 0) return;
		belongTo_[index] = componentElements_.size() - 1;
		componentElements_.back().push_back(index);
		for (const auto& next: revEdge_[index])
			assignComponent(next);
	}

public:
	// belongTo[i]は元のグラフの要素iが属している強連結成分のindex
	const vi& belongTo;
	// belongToの逆像、compoenentElements.size()は強連結成分の個数
	const vvi& componentElements;

	SCC(const EdgeLists& adjacentList)
		: edge_(adjacentList), belongTo(belongTo_), componentElements(componentElements_)
	{
		revEdge_.resize(edge_.size());
		for (int i{}; i < (int)edge_.size(); i++)
			for (const auto& vertex: edge_[i])
				revEdge_[vertex.to].push_back(i);

		vb visited(edge_.size());
		vi revOrder;
		revOrder.reserve(edge_.size());
		for (int i{}; i < (int)edge_.size(); i++)
			backTrack(i, revOrder, visited);

		belongTo_.resize(edge_.size(), -1);
		for (int i{(int)revOrder.size() - 1}; i >= 0; i--)
		{
			const int index{revOrder[i]};
			if (belongTo_[index] >= 0) continue;
			componentElements_.push_back({});
			assignComponent(index);
		}
	}

	// 強連結成分を押しつぶした時のDAGを隣接リストで返す
	vvi makeDAG() const
	{
		std::set<std::pair<int, int>> added;
		vvi dagList(edge_.size());
		for (int from_i{}; from_i < (int)edge_.size(); from_i++)
			for (const auto& to_e: edge_[from_i])
			{
				const int dag_from{belongTo[from_i]}, dag_to{belongTo[to_e.to]};
				if (dag_from == dag_to || added.find({dag_from, dag_to}) != added.end())
					continue;
				dagList[dag_from].push_back(dag_to);
				added.insert({dag_from, dag_to});
			}
		return std::move(dagList);
	}
};

int solve();

int main()
{
	printf("%d\n", solve());

	return 0;
}

int solve()
{
	int N;
	scanf("%d", &N);
	EdgeLists edges(N * (N - 1) / 2);
	using vi = std::vector<int>;
	std::vector<vi> id(N, vi(N, -1));
	{
		int counter{};
		for (int i{}; i < N; i++)
			for (int j{}; j < i; j++)
			{
				id[i][j] = counter;
				counter++;
			}
		for (int i{}; i < N; i++)
			for (int j{i + 1}; j < N; j++)
				id[i][j] = id[j][i];
	}
	for (int i{}; i < N; i++)
	{
		int prev;
		scanf("%d", &prev);
		prev--;
		for (int j{}; j < N - 2; j++)
		{
			int A;
			scanf("%d", &A);
			A--;
			edges[id[i][prev]].push_back({id[i][A]});
			prev = A;
		}
	}

	std::vector<int> rest(edges.size());
	for (auto& e: edges)
		for (auto& f: e)
			rest[f.to]++;
	
	std::vector<int> count(edges.size(), -1);
	std::queue<int> que;
	int max{-1};
	for (int i{}; i < (int)rest.size(); i++)
	{
		if (rest[i] > 0) continue;
		que.push(i);
		count[i] = 1;
	}
	while (!que.empty())
	{
		auto now{que.front()};
		que.pop();
		max = std::max(max, count[now]);
		for (auto& e: edges[now])
		{
			rest[e.to]--;
			if (rest[e.to] == 0)
			{
				if (count[e.to] >= 0) return -1;
				que.push(e.to);
				count[e.to] = count[now] + 1;
			}
		}
	}
	for (auto& e: count)
		if (e == -1)
			return -1;
	return max;
}


Measured memory_bytes: 35456