#include <algorithm>  
#include <iostream>  
#include <sstream>  
#include <string>  
#include <cstring>
#include <vector>  
#include <queue>  
#include <set>  
#include <map>  
#include <cstdio>  
#include <cstdlib>  
#include <cctype>  
#include <cmath>  
#include <list>  
#include <cassert>
#include <ctime>
#include <climits>
using namespace std;  

#define PB push_back  
#define MP make_pair  
#define SZ(v) ((int)(v).size())  
#define FOR(i,a,b) for(int i=(a);i<(b);++i)  
#define REP(i,n) FOR(i,0,n)  
#define FORE(i,a,b) for(int i=(a);i<=(b);++i)  
#define REPE(i,n) FORE(i,0,n)  
#define FORSZ(i,a,v) FOR(i,a,SZ(v))  
#define REPSZ(i,v) REP(i,SZ(v))  
typedef long long ll;
typedef unsigned long long ull;
ll gcd(ll a,ll b) { return b==0?a:gcd(b,a%b); }

const int MAXDIM=185;
const int MAXCOMPLEXITY=16+1;

int h,w;
char g[MAXDIM][MAXDIM+1];

unsigned char bestdn[MAXCOMPLEXITY][MAXDIM][MAXDIM][MAXDIM];
unsigned char bestrt[MAXCOMPLEXITY][MAXDIM][MAXDIM][MAXDIM];


void upd(unsigned char &a,unsigned char b) { a=max(a,b); }
int solve() {
	for(int x=h-1;x>=0;--x) {
		REP(ly,w) FOR(ry,ly,w) {
			bool rowok=ly==ry||g[x][ry]==g[x][ly]&&bestdn[0][x][ly][ry-1]!=0;
			bestdn[0][x][ly][ry]=!rowok?0:x+1>=h||g[x][ly]!=g[x+1][ly]?1:bestdn[0][x+1][ly][ry]+1;
			//if(bestdn[0][x][ly][ry]!=0) printf("bestdn[0][%d][%d][%d]=%d\n",x,ly,ry,bestdn[0][x][ly][ry]);
		}
	}
	for(int y=w-1;y>=0;--y) {
		REP(lx,h) FOR(rx,lx,h) {
			bool colok=lx==rx||g[lx][y]==g[rx][y]&&bestrt[0][y][lx][rx-1]!=0;
			bestrt[0][y][lx][rx]=!colok?0:y+1>=w||g[lx][y]!=g[lx][y+1]?1:bestrt[0][y+1][lx][rx]+1;
			//if(bestrt[0][y][lx][rx]!=0) printf("bestrt[0][%d][%d][%d]=%d\n",y,lx,rx,bestrt[0][y][lx][rx]);
		}
	}
	if(bestdn[0][0][0][w-1]>=h) return 0;
	FOR(c,1,MAXCOMPLEXITY) {
		REP(x,h) REP(ly,w) FOR(ry,ly,w) bestdn[c][x][ly][ry]=0;
		REP(y,w) REP(lx,h) FOR(rx,lx,h) bestrt[c][y][lx][rx]=0;
		REP(x,h) REP(ly,w) FOR(ry,ly,w) {
			int dx=bestdn[c-1][x][ly][ry];
			if(x+dx<h) dx+=bestdn[c-1][x+dx][ly][ry];
			upd(bestdn[c][x][ly][ry],dx);
			if(dx!=0) upd(bestrt[c][ly][x][x+dx-1],ry-ly+1);
		}
		REP(y,w) REP(lx,h) FOR(rx,lx,h) {
			int dy=bestrt[c-1][y][lx][rx];
			if(y+dy<w) dy+=bestrt[c-1][y+dy][lx][rx];
			upd(bestrt[c][y][lx][rx],dy);
			if(dy!=0) upd(bestdn[c][lx][y][y+dy-1],rx-lx+1);
		}
		REP(x,h) REP(ly,w) for(int ry=w-1;ry>ly;--ry) upd(bestdn[c][x][ly][ry-1],bestdn[c][x][ly][ry]);
		REP(y,w) REP(lx,h) for(int rx=h-1;rx>lx;--rx) upd(bestrt[c][y][lx][rx-1],bestrt[c][y][lx][rx]);
		if(bestdn[c][0][0][w-1]>=h) return c;
	}
	assert(false);
	return MAXCOMPLEXITY;
}

void run() {
	scanf("%d%d",&h,&w);
	REP(x,h) scanf("%s",g[x]);
	printf("%d\n",solve());
}

void stress1() {
	REP(rep,100) {
		h=w=MAXDIM;
		REP(x,h) { REP(y,w) g[x][y]=rand()%2==0?'.':'#'; g[x][w]='\0'; }
		if(rep==1) REP(x,h) REP(y,w) g[x][y]=(x+y)%2==0?'.':'#';
		int have=solve();
		printf("%d\n",have);
	}
}

int solvestupid() {
	vector<vector<vector<vector<int>>>> dp(h,vector<vector<vector<int>>>(h,vector<vector<int>>(w,vector<int>(w,INT_MAX-1))));
	FORE(dx,1,h) REPE(lx,h-dx) FORE(dy,1,w) REPE(ly,w-dy) {
		int rx=lx+dx-1,ry=ly+dy-1;
		bool allsame=true; FORE(x,lx,rx) FORE(y,ly,ry) if(g[x][y]!=g[lx][ly]) allsame=false; if(allsame) { dp[lx][rx][ly][ry]=0; continue; }
		FOR(mx,lx,rx) dp[lx][rx][ly][ry]=min(dp[lx][rx][ly][ry],max(dp[lx][mx][ly][ry],dp[mx+1][rx][ly][ry])+1);
		FOR(my,ly,ry) dp[lx][rx][ly][ry]=min(dp[lx][rx][ly][ry],max(dp[lx][rx][ly][my],dp[lx][rx][my+1][ry])+1);
		//printf("lx=%d rx=%d ly=%d ry=%d -> %d\n",lx,rx,ly,ry,dp[lx][rx][ly][ry]);
		assert(dp[lx][rx][ly][ry]!=INT_MAX-1);
	}
	return dp[0][h-1][0][w-1];
}

void stress2() {
	int maxbits=18;
	for(h=1;h<=maxbits;++h) for(w=1;w*h<=maxbits;++w) {
		printf("%d*%d\n",h,w);
		REP(mask,1<<(w*h)) {
			int at=0; REP(x,h) { REP(y,w) g[x][y]=(mask&(1<<at))!=0?'#':'.',++at; g[x][w]='\0'; } assert(at==h*w);
			int have=solve();
			int want=solvestupid();
			if(have!=want) printf("err have=%d want=%d\n",have,want);
		}
	}
}

int main() {
	run();
	//stress1();
	//stress2();
	return 0;
}


Measured memory_bytes: 210560