## 実装

#include <bits/stdc++.h>
#define REP(i, n) for (int i = 0; (i) < (int)(n); ++ (i))
#define REP3(i, m, n) for (int i = (m); (i) < (int)(n); ++ (i))
#define ALL(x) begin(x), end(x)
using namespace std;
template <typename X, typename T> auto vectors(X x, T a) { return vector<T>(x, a); }
template <typename X, typename Y, typename Z, typename... Zs> auto vectors(X x, Y y, Z z, Zs... zs) { auto cont = vectors(y, z, zs...); return vector<decltype(cont)>(x, cont); }
template <typename T> ostream & operator << (ostream & out, vector<T> const & xs) { REP (i, int(xs.size()) - 1) out << xs[i] << ' '; if (not xs.empty()) out << xs.back(); return out; }

bool solve(int h, int w, vector<string> s) {
// transpose
if (h > w) {
vector<string> t(w, string(h, '\0'));
REP (y, h) REP (x, w) {
t[x][y] = s[y][x];
}
swap(h, w);
s = t;
}
assert (h <= w);

// make the table from letters
array<vector<pair<int, int> >, 26> lookup;
REP (y, h) REP (x, w) {
lookup[s[y][x] - 'a'].emplace_back(y, x);
}

auto row_compatible = vectors(h, h, bool());
auto col_compatible = vectors(w, w, bool());
REP (y1, h) REP (y2, h) {
row_compatible[y1][y2] = (multiset<int>(ALL(s[y1])) == multiset<int>(ALL(s[y2])));
}
REP (x1, w) REP (x2, w) {
multiset<int> ms1, ms2;
REP (y, h) {
ms1.insert(s[y][x1]);
ms2.insert(s[y][x2]);
}
col_compatible[x1][x2] = (ms1 == ms2);
}

vector<int> row(h, -1);
vector<int> col(w, -1);
int used_row = 0;
int used_col = 0;

auto check_col = [&](int x) {
assert (col[x] != -1 and col[w - x - 1] != -1);
REP (y, h) if (row[y] != -1) {
if (s[row[y]][col[x]] != s[row[h - y - 1]][col[w - x - 1]]) {
return false;
}
}
return true;
};
auto check_row = [&](int y) {
assert (row[y] != -1 and row[h - y - 1] != -1);
REP (x, w) if (col[x] != -1) {
if (s[row[y]][col[x]] != s[row[h - y - 1]][col[w - x - 1]]) {
return false;
}
}
return true;
};
auto check = [&]() {
REP (y, (h + 1) / 2) if (row[y] != -1) {
if (not check_row(y)) {
return false;
}
}
return true;
};

// exhaustive search
function<bool (int, char)> go = [&](int z, char last_c) {
if (z < h - z - 1) {
REP3 (c, last_c, 'z' + 1) {
for (auto p1 : lookup[c - 'a']) {
int y1, x1; tie(y1, x1) = p1;
if (used_row & (1 << y1)) continue;
if (used_col & (1 << x1)) continue;
row[z] = y1;
col[z] = x1;
used_row ^= (1 << y1);
used_col ^= (1 << x1);
for (auto p2 : lookup[c - 'a']) {
int y2, x2; tie(y2, x2) = p2;
if (used_row & (1 << y2)) continue;
if (used_col & (1 << x2)) continue;
if (not row_compatible[y1][y2]) continue;
if (not col_compatible[x1][x2]) continue;
row[h - z - 1] = y2;
col[w - z - 1] = x2;
used_row ^= (1 << y2);
used_col ^= (1 << x2);
if (check_row(z) and check_col(z)) {
if (go(z + 1, c)) {
return true;
}
}
used_row ^= (1 << y2);
used_col ^= (1 << x2);
row[h - z - 1] = -1;
col[w - z - 1] = -1;
}
used_row ^= (1 << y1);
used_col ^= (1 << x1);
row[z] = -1;
col[z] = -1;
}
}

} else if (used_row != ((1 << h) - 1)) {
REP (y, h) if (not (used_row & (1 << y))) {
row[h / 2] = y;
used_row ^= (1 << y);
if (go(z, last_c)) {
return true;
}
used_row ^= (1 << y);
row[h / 2] = -1;
}

} else if (z < w - z - 1) {
REP (x1, w) {
char c = s[row[h / 2]][x1];
if (used_col & (1 << x1)) continue;
if (c < last_c) continue;
col[z] = x1;
used_col ^= (1 << x1);
REP (x2, w) {
if (used_col & (1 << x2)) continue;
if (not col_compatible[x1][x2]) continue;
col[w - z - 1] = x2;
used_col ^= (1 << x2);
if (check_col(z)) {
if (go(z + 1, c)) {
return true;
}
}
used_col ^= (1 << x2);
col[w - z - 1] = -1;
}
used_col ^= (1 << x1);
col[z] = -1;
}

} else if (used_col != ((1 << w) - 1)) {
REP (x, w) if (not (used_col & (1 << x))) {
col[w / 2] = x;
used_col ^= (1 << x);
if (go(z, last_c)) {
return true;
}
used_col ^= (1 << x);
col[w / 2] = -1;
}

} else {
if (check()) {
return true;
}
}
return false;
};
return go(0, 'a');
}

int main() {
int h, w; cin >> h >> w;
vector<string> s(h);
REP (y, h) cin >> s[y];
cout << (solve(h, w, s) ? "YES" : "NO") << endl;
return 0;
}