// Arup Guha
// 6/25/2025
// Solution to Kattis Problem: Millionaire Madness
// https://open.kattis.com/problems/millionairemadness

using namespace std;
#include <bits/stdc++.h>

// Directions we can move.
const vector<int> DX = {-1,0,0,1};
const vector<int> DY = {0,-1,1,0};

// Stores board.
int r;
int c;
vector<vector<int>> grid;

bool canDo(int jump);
bool inbounds(pair<int,int>& p);

int main() {

    // Read in grid dimensions.
    cin >> r >> c;

    // Read in grid.
    for (int i=0; i<r; i++) {
        vector<int> tmp(c);
        for (int j=0; j<c; j++)
            cin >> tmp[j];
        grid.push_back(tmp);
    }

    // Safe bounds for binary search based on spec.
    int low = 0, high = 1000000000;

    // Run binary search.
    while (low < high) {

        int mid = (low+high)/2;

        // We can do mid, so the answer is this or lower.
        if (canDo(mid))
            high = mid;

        // We can't do mid, so it at least has to be mid+1.
        else
            low = mid+1;
    }

    // Ta da!
    cout << low << endl;
    return 0;
}

// Runs a breadth first search on grid from top left to bottom right assuming we can't jump
// more than jump.
bool canDo(int jump) {

    // Enqueue first item. (No need to store distance since we just want to see
    // if it's reachable...)
    vector<vector<bool>> used(r, vector<bool>(c, false));
    queue<pair<int,int>> q;
    q.push(pair<int,int>{0,0});
    used[0][0] = true;

    // Start BFS.
    while (q.size() > 0) {

        // Get next.
        pair<int,int> cur = q.front(); q.pop();

        // We made it!
        if (cur.first == r-1 && cur.second == c-1) return true;

        // Go in each direction.
        for (int i=0; i<DX.size(); i++) {

            // Generate ith adjacent location.
            pair<int,int> next;
            next.first = cur.first + DX[i];
            next.second = cur.second + DY[i];

            // Not on grid, skip it.
            if (!inbounds(next)) continue;

            // Too high to jump.
            if (grid[next.first][next.second]-grid[cur.first][cur.second] > jump) continue;

            // Been here before.
            if (used[next.first][next.second]) continue;

            // Mark it and add to queue.
            used[next.first][next.second] = true;
            q.push(next);
        }
    }

    // Never made it.
    return false;
}

// Returns true iff pair p is inbounds on the grid.
bool inbounds(pair<int,int>& p) {
    return p.first >= 0 && p.first < r && p.second >= 0 && p.second < c;
}
