// Slower C++ solution:
#include <algorithm>

#include <cassert>

#include <iostream>

#include <vector>

using namespace std;



struct SCC {

    int V = 0;

    vector<vector<int>> adj;

    vector<int> tour_index, low_link;

    int tour;



    vector<int> stack;

    vector<bool> in_stack;

    int S = 0;



    vector<vector<int>> components;

    vector<int> which_component;



    SCC(int v = 0) {

        init(v);

    }



    SCC(const vector<vector<int>> &_adj) {

        init(_adj);

    }



    void init(int v) {

        V = v;

        adj.assign(V, {});

    }



    void init(const vector<vector<int>> &_adj) {

        adj = _adj;

        V = adj.size();

    }



    void add_edge(int a, int b) {

        adj[a].push_back(b);

    }



    // Tarjan's algorithm.

    void dfs(int node) {

        tour_index[node] = tour++;

        low_link[node] = tour_index[node];

        stack[S++] = node;

        in_stack[node] = true;



        for (int neighbor : adj[node])

            if (tour_index[neighbor] < 0) {

                // neighbor is part of our subtree.

                dfs(neighbor);

                low_link[node] = min(low_link[node], low_link[neighbor]);

            } else if (in_stack[neighbor]) {

                // neighbor is our tree ancestor, so it's a candidate for low_link.

                low_link[node] = min(low_link[node], tour_index[neighbor]);

            }



        if (low_link[node] == tour_index[node]) {

            // node is the highest node in an SCC, which includes everything on the stack up to it.

            components.emplace_back();

            vector<int> &component = components.back();

            int x;



            do {

                assert(S > 0);

                x = stack[--S];

                in_stack[x] = false;

                which_component[x] = (int) components.size() - 1;

                component.push_back(x);

            } while (x != node);

        }

    }



    void build() {

        tour_index.assign(V, -1);

        low_link.assign(V, -1);

        which_component.assign(V, -1);



        S = 0;

        stack.resize(V);

        in_stack.assign(V, false);

        tour = 0;



        // Note that Tarjan's algorithm provides the SCCs in reverse topological order.

        components = {};



        for (int i = 0; i < V; i++)

            if (tour_index[i] < 0)

                dfs(i);

    }

};



struct two_sat {

    int n = 0;

    vector<vector<int>> adj;

    vector<bool> assignment;

    SCC scc;



    int inv(int var) {

        return var ^ 1;

    }



    int new_var() {

        adj.emplace_back();

        adj.emplace_back();

        return 2 * n++;

    }



    void implies(int a, int b) {

        adj[a].push_back(b);

        adj[inv(b)].push_back(inv(a));

    }



    void either(int a, int b) {

        adj[inv(a)].push_back(b);

        adj[inv(b)].push_back(a);

    }



    void set_value(int a) {

        adj[inv(a)].push_back(a);

    }



    void equal(int a, int b) {

        implies(a, b);

        implies(inv(a), inv(b));

    }



    void unequal(int a, int b) {

        implies(a, inv(b));

        implies(inv(a), b);

    }



    // Warning: this only creates an implication in the negative direction. It is still possible for

    // a = b = true with and_var = false. In particular, it does not work to call set_value on inv(and_var).

    int create_and(int a, int b) {

        if (a < 0 || b < 0) return max(a, b);

        int result = new_var();

        implies(result, a);

        implies(result, b);

        return result;

    }



    // Warning: this only creates an implication in the positive direction. It is still possible for

    // a = b = false with or_var = true. In particular, it does not work to call set_value on or_var.

    int create_or(int a, int b) {

        if (a < 0 || b < 0) return max(a, b);

        int result = new_var();

        implies(a, result);

        implies(b, result);

        return result;

    }



    int create_at_most_one(int a, int b) {

        if (a < 0 || b < 0) return max(a, b);

        either(inv(a), inv(b));

        return create_or(a, b);

    }



    template<typename T_iterable>

    int create_at_most_one(const T_iterable &vars) {

        int aux = -1;



        for (int var : vars)

            aux = create_at_most_one(aux, var);



        return aux;

    }



    bool solve() {

        scc.init(adj);

        scc.build();



        for (int i = 0; i < n; i++)

            if (scc.which_component[2 * i] == scc.which_component[2 * i + 1])

                return false;



        assignment.resize(2 * n);

        vector<bool> done(n, false);



        // Tarjan's algorithm provides the SCCs in reverse topological order.

        for (auto &component : scc.components)

            for (int x : component) {

                assignment[x] = !done[x / 2];

                done[x / 2] = true;

            }



        for (int i = 0; i < n; i++)

            assert(assignment[2 * i] ^ assignment[2 * i + 1]);



        return true;

    }

};





const int INF = 1e9 + 5;



int N, T;

vector<pair<int, int>> sorted;

two_sat solver;

vector<int> tree;



void build_tree() {

    T = 2 * N;

    tree.assign(2 * T, -1);



    for (int t = 1; t < 2 * T; t++)

        tree[t] = solver.new_var();



    for (int t = 2; t < 2 * T; t++)

        solver.implies(tree[t / 2], tree[t]);



    for (int t = 0; t < T; t++)

        solver.implies(tree[T + t], solver.inv(sorted[t].second));

}



void add_range(int from, int start, int end) {

    for (start += T, end += T; start < end; start /= 2, end /= 2) {

        if (start & 1)

            solver.implies(from, tree[start++]);



        if (end & 1)

            solver.implies(from, tree[--end]);

    }

}



bool possible(int d) {

    solver = two_sat();



    for (int i = 0; i < N; i++)

        solver.new_var();



    build_tree();



    for (int i = 0; i < T; i++) {

        int start = lower_bound(sorted.begin(), sorted.end(), make_pair(sorted[i].first - d + 1, 0)) - sorted.begin();

        int end = lower_bound(sorted.begin(), sorted.end(), make_pair(sorted[i].first + d, 0)) - sorted.begin();

        add_range(sorted[i].second, start, i);

        add_range(sorted[i].second, i + 1, end);

    }



    return solver.solve();

}



int main() {

    ios::sync_with_stdio(false);

    cin.tie(nullptr);



    cin >> N;



    for (int i = 0; i < N; i++) {

        int x, y;

        cin >> x >> y;

        sorted.emplace_back(x, 2 * i);

        sorted.emplace_back(y, 2 * i + 1);

    }



    sort(sorted.begin(), sorted.end());

    int low = 0, high = INF;



    while (low < high) {

        int mid = (low + high + 1) / 2;



        if (possible(mid))

            low = mid;

        else

            high = mid - 1;

    }



    cout << low << '\n';

}


// Faster C++ solution for the same problem (measured speedup: 1.3226699218186555x):
#include <algorithm>

#include <cassert>

#include <iostream>

#include <vector>

using namespace std;



struct two_sat {

    int n = 0;

    vector<vector<int>> adj;

    vector<bool> assignment;



    int inv(int var) {

        return var ^ 1;

    }



    int new_var() {

        adj.emplace_back();

        adj.emplace_back();

        return 2 * n++;

    }



    void implies(int a, int b) {

        adj[a].push_back(b);

        adj[inv(b)].push_back(inv(a));

    }



    void either(int a, int b) {

        adj[inv(a)].push_back(b);

        adj[inv(b)].push_back(a);

    }



    void set_value(int a) {

        adj[inv(a)].push_back(a);

    }



    void equal(int a, int b) {

        implies(a, b);

        implies(inv(a), inv(b));

    }



    void unequal(int a, int b) {

        implies(a, inv(b));

        implies(inv(a), b);

    }



    // Warning: this only creates an implication in the negative direction. It is still possible for

    // a = b = true with and_var = false. In particular, it does not work to call set_value on inv(and_var).

    int create_and(int a, int b) {

        if (a < 0 || b < 0) return max(a, b);

        int result = new_var();

        implies(result, a);

        implies(result, b);

        return result;

    }



    // Warning: this only creates an implication in the positive direction. It is still possible for

    // a = b = false with or_var = true. In particular, it does not work to call set_value on or_var.

    int create_or(int a, int b) {

        if (a < 0 || b < 0) return max(a, b);

        int result = new_var();

        implies(a, result);

        implies(b, result);

        return result;

    }



    int create_at_most_one(int a, int b) {

        if (a < 0 || b < 0) return max(a, b);

        either(inv(a), inv(b));

        return create_or(a, b);

    }



    template<typename T_iterable>

    int create_at_most_one(const T_iterable &vars) {

        int aux = -1;



        for (int var : vars)

            aux = create_at_most_one(aux, var);



        return aux;

    }



    bool solve() {

        assignment.assign(2 * n, false);

        vector<bool> vis[2];

        vector<int> q[2];

        vis[0].assign(2 * n, false);

        vis[1].assign(2 * n, false);

        q[0].resize(2 * n);

        q[1].resize(2 * n);



        for (int i = 0; i < n; i++) {

            if (!assignment[2 * i] && !assignment[2 * i + 1]) {

                // we have to do the search

                bool bad[2] = {false, false};

                int qs[2] = {0, 0};

                int qi[2] = {0, 0};

                int qt[2] = {0, 0};



                for (int z = 0; z < 2; z++) {

                    vis[z][2 * i + z] = true;

                    q[z][qt[z]++] = 2 * i + z;

                }



                while (true) {

                    if (bad[0] && bad[1])

                        return false;



                    for (int z = 0; z < 2; z++) {

                        if (bad[z])

                            continue;



                        if (qs[z] >= qt[z]) {

                            // we're done, so we should finalize it

                            for (int ind = 0; ind < qt[z]; ind++)

                                assignment[q[z][ind]] = true;



                            bad[!z] = true;

                            goto done;

                        }



                        int v = q[z][qs[z]];



                        if (qi[z] == int(adj[v].size())) {

                            qs[z]++;

                            qi[z] = 0;

                            continue;

                        }



                        int u = adj[v][qi[z]];

                        assert(!assignment[inv(u)]);



                        if (vis[z][inv(u)]) {

                            bad[z] = true;

                        } else if (!assignment[u] && !vis[z][u]) {

                            vis[z][u] = true;

                            q[z][qt[z]++] = u;

                        }



                        qi[z]++;

                    }

                }



done:

                assert(bad[0] ^ bad[1]);



                // reset vis

                for (int z = 0; z < 2; z++)

                    for (int ind = 0; ind < qt[z]; ind++)

                        vis[z][q[z][ind]] = false;

            }



            assert(assignment[2 * i] ^ assignment[2 * i + 1]);

        }



        return true;

    }

};





const int INF = 1e9 + 5;



int N, T;

vector<pair<int, int>> sorted;

two_sat solver;

vector<int> tree;



void build_tree() {

    T = 2 * N;

    tree.assign(2 * T, -1);



    for (int t = 1; t < 2 * T; t++)

        tree[t] = solver.new_var();



    for (int t = 2; t < 2 * T; t++)

        solver.implies(tree[t / 2], tree[t]);



    for (int t = 0; t < T; t++)

        solver.implies(tree[T + t], solver.inv(sorted[t].second));

}



void add_range(int from, int start, int end) {

    for (start += T, end += T; start < end; start /= 2, end /= 2) {

        if (start & 1)

            solver.implies(from, tree[start++]);



        if (end & 1)

            solver.implies(from, tree[--end]);

    }

}



bool possible(int d) {

    solver = two_sat();



    for (int i = 0; i < N; i++)

        solver.new_var();



    build_tree();



    for (int i = 0; i < T; i++) {

        int start = lower_bound(sorted.begin(), sorted.end(), make_pair(sorted[i].first - d + 1, 0)) - sorted.begin();

        int end = lower_bound(sorted.begin(), sorted.end(), make_pair(sorted[i].first + d, 0)) - sorted.begin();

        add_range(sorted[i].second, start, i);

        add_range(sorted[i].second, i + 1, end);

    }



    return solver.solve();

}



int main() {

    ios::sync_with_stdio(false);

    cin.tie(nullptr);



    cin >> N;



    for (int i = 0; i < N; i++) {

        int x, y;

        cin >> x >> y;

        sorted.emplace_back(x, 2 * i);

        sorted.emplace_back(y, 2 * i + 1);

    }



    sort(sorted.begin(), sorted.end());

    int low = 0, high = INF;



    while (low < high) {

        int mid = (low + high + 1) / 2;



        if (possible(mid))

            low = mid;

        else

            high = mid - 1;

    }



    cout << low << '\n';

}