#include <bits/stdc++.h>

#define TYPE(c)   remove_reference_t<decltype(c)>
#define REP(i, n) for(TYPE(n) i = 0; i < n; i++)
#define FOR(v, c) for(TYPE(c.begin()) v = c.begin(); v != c.end(); v++)
#define ALL(c)    c.begin(), c.end()
#define SORT(c)   sort(ALL(c))
#define RSORT(c)  sort(ALL(c), greater<TYPE(c)::value_type>())
#define UNIQUE(c) c.erase(unique(ALL(c)), c.end())

using namespace std;
using ll = long long;

constexpr int MOD  = (int)1e9 + 7;
constexpr int INF  = (int)1e9 + 1;
constexpr ll  LINF = (ll)1e18 + 1;
template<typename T> constexpr bool chmax(T& a, const T& b)
{ if(a < b) { a = b; return true; } else { return false; } }
template<typename T> constexpr bool chmin(T& a, const T& b)
{ if(b < a) { a = b; return true; } else { return false; } }
template<typename S, typename T> ostream& operator<<(ostream &os, const pair<S, T>& p)
{ return os << "(" << p.first << "," << p.second << ")"; }
ostream& operator<<(ostream &os, const string& s)
{ FOR(v, s) os << *v; return os; }
template<typename T> auto operator<<(ostream& os, const T& t)
    -> typename conditional<true, ostream&, decltype(*t.begin())>::type
{ os << "["; FOR(v, t) { if(v != t.begin()) os << ","; os << *v; } os << "]"; return os; }

class Graph {
    using ll = long long;
public:
    struct Edge {
        enum class Type { Directed, Undirected };
        int  src, dst;
        ll   cost;
        bool validity;
        Edge(const int& _src, const int& _dst, const ll& _cost = 0) :
            src(_src), dst(_dst), cost(_cost), validity(true) { }
        friend std::ostream& operator <<(std::ostream& os, const Edge& e) {
            os << "src : " << e.src << ", " << "dst : " << e.dst << ", " << "cost : " << e.cost;
            return os;
        }
    };

    struct Vertex {
        ll cost;
        explicit Vertex(const ll& _cost) : cost(_cost) { }
        friend std::ostream& operator <<(std::ostream& os, const Vertex& v) {
            os << "cost : " << v.cost; return os;
        }
    };

    Graph(const int&        num_vertex,
          const Edge::Type& edge_type,
          const ll&         vertex_init_cost = (ll)1e9 + 1) :
        vertices_(num_vertex, Vertex(vertex_init_cost)),
        edges_(num_vertex),
        num_vertex_(num_vertex),
        num_edge_(0),
        total_edge_cost_(0),
        total_vertex_cost_(num_vertex * vertex_init_cost),
        edge_type_(edge_type) { }

    const std::vector<Vertex>& getVertices() const { return vertices_; }
    const std::vector<std::vector<Edge>>& getEdges() const { return edges_; }
    const ll& getTotalEdgeCost() const { return total_edge_cost_; }
    const ll& getTotalVertexCost() const { return total_vertex_cost_; }
    const int& getNumVertex() const { return num_vertex_; }
    const int& getNumEdge() const { return num_edge_; }
    const Edge::Type& getEdgeType() const { return edge_type_; }

    void setCostToAllVertex(const ll& val) {
        total_vertex_cost_ = vertices_.size() * val;
        std::fill(vertices_.begin(), vertices_.end(), Vertex(val));
    };

    void setCostToSpecificVertex(const int& index, const ll& val) {
        total_vertex_cost_    += val - vertices_[index].cost;
        vertices_[index].cost  = val;
    };

    int addVertex(const ll& cost = (ll)1e9 + 1) {
        vertices_.emplace_back(cost); edges_.emplace_back();
        num_vertex_++;
        total_vertex_cost_ += cost;
        return num_vertex_ - 1;
    }

    void addEdge(const int& src, const int& dst, const ll& cost = 1) {
        edges_[src].emplace_back(src, dst, cost);
        if(edge_type_ == Edge::Type::Undirected) edges_[dst].emplace_back(dst, src, cost);
        num_edge_++;
        total_edge_cost_ += cost;
    }

    void invalidateEdge(const int& src, const int& dst) {
        auto invalidate = [&](const int& src, const int& dst) -> ll {
                              for(auto& edge : edges_[src])
                                  if(edge.dst == dst) { edge.validity = false; return edge.cost; }
                              return 0LL;
                          };
        total_edge_cost_ -= invalidate(src, dst);
        if(edge_type_ == Edge::Type::Undirected) invalidate(dst, src);
        num_edge_--;
    }

    friend std::ostream& operator <<(std::ostream& os, const Graph& g) {
        os << std::endl
           << "--- Vertex Information ---" << std::endl;
        for(size_t i = 0; i < g.vertices_.size(); i++) {  os << "[[" << i << "] " << g.vertices_[i] << "]" << std::endl; }
        os << "---- Edge Information ----" << std::endl;
        for(const auto& v : g.edges_) { for(const auto& e : v) { os << "[" << e << "]" << std::endl; } }
        os << "--------------------------";
        return os;
    }
private:
    std::vector<Vertex>            vertices_;
    std::vector<std::vector<Edge>> edges_;
    int                            num_vertex_;
    int                            num_edge_;
    ll                             total_edge_cost_;
    ll                             total_vertex_cost_;
    const Edge::Type               edge_type_;
};

/**
 *  最小全域木を求める(Kruskal法)
 *  NOTE: 計算量O(|E|log|V|), Dependencies: comp-Graph,
 *  @Return: 最小全域木を返す グラフが非連結の場合、連結成分ごとに最小全域木を構成する
 */
Graph kruskal(const Graph& g) {
    if(g.getEdgeType() != Graph::Edge::Type::Undirected) {
        throw std::logic_error("Graph type is invalid");
    }
    auto& vs = g.getVertices();
    auto& es = g.getEdges();
    Graph mst(vs.size(), Graph::Edge::Type::Undirected);
    std::vector<Graph::Edge> mst_e;
    mst_e.reserve(g.getNumEdge() * 2);
    for(const auto& ee : es) for(const auto& e : ee) mst_e.push_back(e);
    auto sort_rule = [](const Graph::Edge& l, const Graph::Edge& r) { return l.cost < r.cost; };
    sort(mst_e.begin(), mst_e.end(), sort_rule);
    struct UF {
        struct N{ll p,r;N(const ll&_p=0,const ll&_r=0):p(_p),r(_r){}};std::vector<N>n;
        explicit UF(const ll&nn){n.reserve(nn);for(ll i=0;i<nn;i++){n.emplace_back(i);}}
        ll R(const ll&x){return(n[x].p==x)?x:n[x].p=R(n[x].p);}
        void U(const ll&x,const ll&y){ll w=R(x);ll z=R(y);
            if(w!=z){if(n[w].r<n[z].r)std::swap(w,z);n[z].p=w;if(n[w].r==n[z].r)n[w].r++;}}
        bool S(const ll&x,const ll&y){return R(x)==R(y);}
    };
    UF uf(vs.size());
    for(const auto& e : mst_e) {
        if(!uf.S(e.src, e.dst)) {
            mst.addEdge(e.src, e.dst, e.cost);
            uf.U(e.src, e.dst);
        }
    }
    return mst;
}

/**
 *  BFSによる二部グラフ判定
 *  NOTE: Dependencies: comp-Graph, 0と1で塗り分ける
 */
// 使用例
// g.setCostToAllVertex(INF);
// judge_bipartite_graph(g, 0, INF);
bool judge_bipartite_graph(Graph&     g,
                           const int& start,
                           const ll&  inf_val = (ll)1e18 + 1) {
    auto& vs = g.getVertices();
    auto& es = g.getEdges();
    std::queue<int> q;
    q.push(start);
    g.setCostToSpecificVertex(start, 0);
    while(!q.empty()) {
        auto top = q.front(); q.pop();
        for(const auto& e : es[top]) {
            if(vs[e.dst].cost == inf_val) {
                g.setCostToSpecificVertex(e.dst, (vs[e.src].cost == 1) ? 0 : 1);
                q.push(e.dst);
            }
            else if(vs[e.dst].cost == vs[e.src].cost) {
                return false;
            }
        }
    }
    return true;
}

/**
 *  全頂点間の最短距離を求める(Warshall-Floyd法)
 *  NOTE: 計算量O(|V|^3)
 *  @Return: array[i][j] :: 頂点iから頂点jまでの最短距離
 */
// 使用例(グラフの直径)
// auto table    = warshall_floyd(g);
// auto diameter = 0;
// REP(i, g.getVertices().size()) {
//     REP(j, g.getVertices().size()) {
//         if(j <= i) continue;
//         chmax(diameter, table[j][i] + 1);
//     }
// }
std::vector<std::vector<ll>> warshall_floyd(const Graph& g,
                                            const ll& inf_val = (ll)1e18 + 1) {
    auto& vs = g.getVertices();
    auto& es = g.getEdges();
    std::vector<std::vector<ll>> dp(vs.size(), std::vector<ll>(vs.size(), inf_val));
    for(const auto& ee : es) for(const auto& e : ee) dp[e.src][e.dst] = e.cost;
    for(size_t i = 0; i < vs.size(); i++)
        for(size_t j = 0; j < vs.size(); j++)
            for(size_t k = 0; k < vs.size(); k++)
                dp[j][k] = std::min(dp[j][k], dp[j][i] + dp[i][k]);
    return dp;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);

    int N;
    std::cin >> N;
    Graph g(N, Graph::Edge::Type::Undirected, INF);
    REP(i, N) {
        string s;
        std::cin >> s;
        REP(j, N) {
            if(j <= i) continue;
            if(s[j] == '1') g.addEdge(i, j, 1);
        }
    }

    if(judge_bipartite_graph(g, 0, INF)) {
        auto table    = warshall_floyd(g);
        ll   diameter = 0;
        REP(i, g.getVertices().size()) {
            REP(j, g.getVertices().size()) {
                if(j <= i) continue;
                chmax(diameter, table[j][i] + 1);
            }
        }
        std::cout << diameter << std::endl;
    }
    else {
        std::cout << -1 << std::endl;
    }

    return 0;
}


Measured memory_bytes: 1536