#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<map>
#include<deque>
#include<cstring>
#include<climits>
#include<cassert>
#include<cctype>

#define REP(i,s,n) for(int i=s;i<n;++i)
#define rep(i,n) REP(i,0,n)

using namespace std;

int dx[] = {0,1};
int dy[] = {1,0};

struct Data {
  int x,y;
  char v;
  bool operator < ( const Data &data ) const {
    if( x != data.x ) return x < data.x;
    if( y != data.y ) return y < data.y;
    return v < data.v;
  }
};

bool used[60][60];
void makeGraph(vector<string> &C,vector<vector<int>> &G,vector<char> &ws) {
  int H = C.size(), W = C[0].size();
  map<Data,int> mp;
  int _cnt = 0;
  rep(i,H) rep(j,W) if( ( i == 0 && j == 0 ) || ( i == H-1 && j == W-1 ) || isalpha(C[i][j]) ) {
    mp[(Data){j,i,C[i][j]}] = _cnt++;
  }
  int V = mp.size();
  G.resize(V);
  rep(i,V) G[i].clear();
  ws.resize(V);

  rep(i,H) rep(j,W) if( ( i == 0 && j == 0 ) || ( i == H-1 && j == W-1 ) || isalpha(C[i][j]) ) {
    int sp = mp[(Data){j,i,C[i][j]}];
    ws[sp] = C[i][j];
    memset(used,false,sizeof used);
    deque<int> deq;
    deq.push_back(j+i*W);

    while( !deq.empty() ) {
      int cur = deq.front(); deq.pop_front();
      int x = cur % W, y = cur / W;
      rep(k,2) {
	int nx = x + dx[k], ny = y + dy[k];

	if( !( 0 <= nx && nx < W && 0 <= ny && ny < H ) ) continue;
	if( used[ny][nx] ) continue;
	if( C[ny][nx] == '#' ) continue;

	used[ny][nx] = true;
	if( isalpha(C[ny][nx]) || ( nx == W-1 && ny == H-1 ) ) G[sp].push_back(mp[(Data){nx,ny,C[ny][nx]}]);
	deq.push_back(nx+ny*W);
	
      }
    }
  }
}

bool visit(const vector<vector<int>>& G,int v,vector<int>& order,vector<int>& color){
  color[v] = 1;
  rep(i,(int)G[v].size()) {
    int e = G[v][i];
    if(color[e] == 2)continue;
    if(color[e] == 1)return false;
    if(!visit(G,e,order,color))return false;
  }
  order.push_back(v);
  color[v] = 2;
  return true;
}


bool topologicalSort(const vector<vector<int>>& G,vector<int>& order){
  int SizeG = G.size();
  vector<int> color(SizeG); 
  for(int u=0;u<SizeG;u++) if(!color[u] && !visit(G,u,order,color)) return false;
  reverse(order.begin(),order.end());
  return true;
}

bool canPut(char a,char A) {
  if( !islower(a) || !isupper(A) ) return false;
  return toupper(a) == A;
}

#define MAX_V 600
bool hasEdge[MAX_V][MAX_V];
int dp[MAX_V][MAX_V];
void compute(vector<string> &C) {
  if( C.size() == 1 && C[0].size() == 1 ) { puts("0"); return; }
  // DAG???
  vector<vector<int>> G;
  vector<char> ws;
  makeGraph(C,G,ws);

  {
    bool fin = true;
    rep(i,(int)G[0].size()) if( G[0][i] == (int)G.size()-1 ) { fin = false; break; }
    if( fin ) { puts("-1"); return; }
  }

  // topological sort
  vector<int> order;
  assert( topologicalSort(G,order) );

  // DP
  int V = G.size();

  memset(hasEdge,false,sizeof hasEdge);
  rep(i,V) rep(j,(int)G[i].size()) hasEdge[i][G[i][j]] = true;
  assert( V < MAX_V );
  memset(dp,0,sizeof dp);
  REP(len,2,V+1) {
    rep(i,V) {
      int j = i + len - 1;
      if( j >= V ) break;
      int sp = order[i];
      int ep = order[j];
      if( !hasEdge[sp][ep] ) continue;

      bool put = false;
      if( canPut(ws[sp],ws[ep]) ) {
	put = true;
	dp[sp][ep] = max(dp[sp][ep],1);
      }

      rep(k,(int)G[sp].size()) {
	int nex = G[sp][k];
	rep(l,(int)G[nex].size()) {
	  int nnex = G[nex][l];
	  if( !( nnex == ep || hasEdge[nnex][ep] ) ) continue;
	  dp[sp][ep] = max(dp[sp][ep],
			   max(dp[nex][nnex],dp[sp][nex]+dp[nnex][ep]));
	  if( put && nnex != ep ) {
	    dp[sp][ep] = max(dp[sp][ep],
			     dp[nex][nnex]+1);
	  }
	}
      }
    }
  }
  cout << dp[0][V-1] << endl;
}

int main() {
  int H,W;
  while( cin >> H >> W, H|W ) {
    vector<string> C(H);
    rep(i,H) cin >> C[i];
    compute(C);
  }
  return 0;
}

Measured memory_bytes: 5156