#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <climits>
#include <vector>
#include <string>
#include <queue>
#include <deque>
#include <list>
#include <stack>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <algorithm>

#define int long long
#define MOD7 1000000007
#define MOD9 1000000009

#define rep(i, n) for (int i = 0; i < (n); i++)
#define itrep(i, a) for (auto i = (a).begin(); i != (a).end(); i++)
#define REP(i, a, n) for (int i = (a); i <= (n); i++)
#define all(a) (a).begin(), (a).end()
#define mp(a, b) make_pair((a), (b))

using namespace std;

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

template<class T> void inputVector(vector<T>& v, int n) {
    v.resize(n);
    for (int i = 0; i < v.size(); i++) cin >> v[i];
}

int H, W;

bool isRevStr(string S, string T) {
	reverse(all(T));
	return S == T;
}

void swapCol(vector<string> &board, int x1, int x2) {
	rep(y, H) {
		swap(board[y][x1], board[y][x2]);
	}
}

bool solve4(vector<string> &board) {
	// 条件を満たすかチェック
	rep(y, H) rep(x, W) {
		if (board[y][x] != board[H - y - 1][W - x - 1]) return false;
	}
	return true;
}

bool solve3(vector<string> &board, int ay) {
	if (H <= 3 && ay == 1) return solve4(board);
	// ay に対応する行を決める
	REP(y, ay + 1, H - 1 - ay) {
		if (!isRevStr(board[ay], board[y])) continue;
		swap(board[y], board[H - 1 - ay]);
		if (ay == H / 2 - 1 ? solve4(board) : solve3(board, ay + 1)) return true;
		swap(board[y], board[H - 1 - ay]);
	}

	return false;
}

bool solve2(vector<string> &board, int ax) {
	// ax に対応する列を決める
	REP(x, ax + 1, W - 1 - ax) {
		if (board[H - 1][x] != board[0][ax]) continue;
		swapCol(board, x, W - 1 - ax);
		if (ax == W / 2 - 1 ? solve3(board, 1) : solve2(board, ax + 1)) return true;
		swapCol(board, x, W - 1 - ax);
	}
	return false;
}

bool solve1(vector<string> &board) {
	// 一番上の行に対応する行を決める
	vector<int> cnt(26, 0);
	rep(x, W) cnt[board[0][x] - 'a']++;

	REP(y, 1, H - 1) {
		vector<int> cnt2(26, 0);
		rep(x, W) cnt2[board[y][x] - 'a']++;
		if (cnt != cnt2) continue;

		swap(board[y], board[H - 1]);
		if (solve2(board, 0)) return true;
		swap(board[y], board[H - 1]);
	}
	
	return false;
}

bool solveCorner(vector<string> &board) {
	vector<bool> flags(26, false);
	rep(y, H) rep(x, W) {
		flags[board[y][x] - 'a'] = !flags[board[y][x] - 'a'];
	}
	int cnt = 0;
	rep(i, 26) cnt += flags[i];

	int base = max(H, W);
	return base % 2 == 0 && cnt == 0 || base % 2 == 1 && cnt == 1;
}

bool solve(vector<string> &board) {
	// 一番上に持ってくる行を決める
	rep(y, H) {
		swap(board[0], board[y]);
		if (solve1(board)) return true;
		swap(board[0], board[y]);
	}

	return false;
}

bool solve00(vector<string> &board) {
	// 真ん中に持ってくる列を決める
	if (W % 2 == 0) return solve(board);
	rep(x, W) {
		swapCol(board, x, W / 2);
		if (solve(board)) return true;
		swapCol(board, x, W / 2);
	}
	return false;
}

bool solve0(vector<string> &board) {
	if (H == 1 || W == 1) return solveCorner(board);

	// 真ん中に持ってくる行を決める
	if (H % 2 == 0) return solve(board);
	rep(y, H) {
		swap(board[y], board[H / 2]);
		return solve00(board);
		swap(board[y], board[H / 2]);
	}
	return false;
}

unsigned long randXor() {
	static unsigned long x = 123456789, y = 362436069, z = 521288629, w = 88675123;
	unsigned long t = (x ^ (x << 11));
	x = y; y = z; z = w;
	return (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)));
}

signed main() {
	cin >> H >> W;

	vector<string> board;
	inputVector(board, H);

	bool flag = false;
	rep(i, 10) {
		flag |= solve0(board);
		if (flag) break;
		rep(i, 100) swap(board[randXor() % H], board[0]);
		rep(i, 100) swapCol(board, 0, randXor() % W);
	}
	cout << (flag ? "YES" : "NO") << endl;

    return 0;
}


Measured memory_bytes: 256