// #include <bits/stdc++.h>
#include <iostream> // cout, endl, cin
#include <string> // string, to_string, stoi
#include <vector> // vector
#include <algorithm> // min, max, swap, sort, reverse, lower_bound, upper_bound
#include <utility> // pair, make_pair
#include <tuple> // tuple, make_tuple
#include <cstdint> // int64_t, int*_t
#include <cstdio> // printf
#include <map> // map
#include <queue> // queue, priority_queue
#include <set> // set
#include <stack> // stack
#include <deque> // deque
#include <unordered_map> // unordered_map
#include <unordered_set> // unordered_set
#include <bitset> // bitset
#include <cctype> // isupper, islower, isdigit, toupper, tolower

#define int long long

// loop
#define FOR(i, a, b) for (int i = (a); i < (b); i++) 	// a ~ b-1	(ascending)
#define REP(i, n) FOR(i, 0, n)							// 0 ~ n-1
#define NREP(i, n) FOR(i, 1, n + 1)						// 1 ~ n
#define RFOR(i, a, b) for (int i = (a); i >= (b); i--)	// a ~ b	(descending)
#define RREP(i, n) RFOR(i, n, 0)						// n ~ 0
#define RNREP(i, n) RFOR(i, n, 1)						// n ~ 1
 
// container operation
#define all(v) v.begin(), v.end()
#define EACH(i, c) for (auto i = (c).begin(); i != (c).end(); i++)
#define ASORT(c) std::sort((c).begin(), (c).end())
#define DSORT(c) std::sort((c).begin(), (c).end(), std::greater<typeof((c).front())>())
#define SIZE(x) ((int)(x).size())
// union of two containers
template <typename T>
void container_union(T &c1, T &c2, T &c3) {
  std::vector<int> vec;
  std::set_union(c1.begin(), c1.end(),
                 c2.begin(), c2.end(),
                 std::back_inserter(vec));
  T tmp(vec.begin(), vec.end());
  c3 = tmp;
}

// chmax, chmin
template <typename T>
bool chmax(T &a, const T& b) {
  if (a < b) {
    a = b;  // aをbで更新
    return true;
  }
  return false;
}
template <typename T>
bool chmin(T &a, const T& b) {
  if (a > b) {
    a = b;  // aをbで更新
    return true;
  }
  return false;
}
 
// debug
#define check(x) std::cout << #x << " = " << x << '\n'
 
// print
#define cout(x) std::cout << x << '\n'
 
// type alias
using VI = std::vector<int>;
using VII = std::vector<VI>;
using VB = std::vector<bool>;
using VBB = std::vector<VB>;
using VS = std::vector<std::string>;
using PII = std::pair<int, int>;

// grid-search problem
std::vector<int> dx = {1, -1, 0,  0};
std::vector<int> dy = {0,  0, 1, -1};

// yes/no
std::string yes = "Yes";
std::string no = "No";
std::string YES = "YES";
std::string NO = "NO";

void solve();

signed main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  solve();
  return 0;
}

void solve() {
  int h, w;
  std::cin >> h >> w;
  std::vector<std::vector<char>> m(h, std::vector<char>(w));
  REP(i, h) {
    REP(j, w) {
      std::cin >> m.at(i).at(j);
    }
  }

  int black = 0;
  std::queue<PII> q1, q2;
  REP(i, h) {
    REP(j, w) {
      if (m.at(i).at(j) == '#') {
        q1.push(PII(i, j));
        black++;
      }
    }
  }
  
  int count = 0;
  while (true) {
    if (black == h*w) break;
    while (!q1.empty()) {
      PII p = q1.front();
      q1.pop();
      int x = p.first; int y = p.second;
      // cout(x << " " << y);
      REP(i, 4) {
        int nx = x + dx.at(i); int ny = y + dy.at(i);
        if (nx < 0 || nx > h-1 || ny < 0 || ny > w-1) continue;
        if (m.at(nx).at(ny) == '.') {
          // cout("  " << nx << " " << ny);
          m.at(nx).at(ny) = '#';
          q2.push(PII(nx, ny));
          black++;
        }
      }
    }
    count++;
    if (black == h*w) break;
    while (!q2.empty()) {
      PII p = q2.front();
      q2.pop();
      int x = p.first; int y = p.second;
      // cout(x << " " << y);
      REP(i, 4) {
        int nx = x + dx.at(i); int ny = y + dy.at(i);
        if (nx < 0 || nx > h-1 || ny < 0 || ny > w-1) continue;
        if (m.at(nx).at(ny) == '.') {
          // cout("  " << nx << " " << ny);
          m.at(nx).at(ny) = '#';
          q1.push(PII(nx, ny));
          black++;
        }
      }
    }
    count++;
  }
  cout(count);
}


Measured memory_bytes: 17588