#include <algorithm>
#include <array>
#include <bitset>
#include <cassert>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstring>
#include <deque>
#include <functional>
#include <iomanip>
#include <locale>
#include <iostream>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <sstream>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#define REP(i,n) for(int i=0;i<(int)(n);i++)
#define ALL(x) (x).begin(),(x).end()

using namespace std;

using ll = long long;
using ld = long double;

template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); }
template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); }
template <typename T> int len(const T &x) { return x.size(); }

template<typename T>
vector<T> table(int n, T v) { return vector<T>(n, v); }

template <class... Args>
auto table(int n, Args... args) {
  auto val = table(args...);
  return vector<decltype(val)>(n, move(val));
}

struct yes_no : numpunct<char> {
  string_type do_truename()  const { return "YES"; }
  string_type do_falsename() const { return "NO"; }
};

int H, W;

bool check(vector<string> &left, vector<string> &right, vector<string> &rest) {
  // cout << left.size() << " " << right.size() << " " << rest.size() << endl;
  multiset<string> se;
  REP(i,W) {
    string s;
    const int n = left.size();
    REP(j,n) s += left[j][i];
    if (!rest.empty()) s += rest[0][i];
    REP(j,n) s += right[n-j-1][i];
    se.insert(s);
    assert (int(s.size()) == H);
  }
  // cout << rest.size() << endl;
  if (se.size() % 2 == 0) {
    while (!se.empty()) {
      auto it = begin(se);
      string s = *it;
      se.erase(it);
      reverse(ALL(s));
      auto it2 = se.find(s);
      if (it2 == end(se)) return false;
      else se.erase(it2);
    }
    return true;
  }
  else {
    const multiset<string> se2 = se;
    for (string s: se2) {
      se = se2;
      string t = s;
      reverse(ALL(t));
      if (s != t) continue;
      auto it = se.find(s);
      se.erase(it);
      while (!se.empty()) {
        auto it = begin(se);
        string s = *it;
        se.erase(it);
        reverse(ALL(s));
        auto it2 = se.find(s);
        if (it2 == end(se)) break;
        else se.erase(it2);
      }
      if (se.empty()) return true;
      se = se2;
    }
    return false;
  }
}

bool dfs(vector<string> left, vector<string> right, vector<string> center, vector<string> rest) {
  if (rest.size() == 0) return check(left, right, center);
  const string l = rest.back();
  if (center.empty()) {
    center.push_back(l);
    rest.pop_back();
    if (dfs(left, right, center, rest)) return true;
    rest.push_back(l);
    center.pop_back();
  }
  if (rest.size() >= 2) {
    left.push_back(l);
    rest.pop_back();

    const string r = rest.back();
    rest.pop_back();
    right.push_back(r);
    if (dfs(left, right, center, rest)) return true;
    const int n = rest.size();
    REP(i,n) {
      swap(rest[i], right.back());
      if (dfs(left, right, center, rest)) return true;
      swap(rest[i], right.back());
    }
    left.pop_back();
    right.pop_back();
    rest.push_back(r);
    rest.push_back(l);
  }
  return false;
}


void solve(vector<string> S) {
  vector<string> left, right, center;
  cout << dfs(left, right, center, S) << endl;
}

int main() {
  locale loc(locale(), new yes_no);
  cout << boolalpha << setprecision(12) << fixed;
  cout.imbue(loc);
	scanf("%d", &H);
	vector<string> S(H-1+1);
	scanf("%d", &W);
	for (int i = 0 ; i <= H-1 ; i++) {
	  cin >> S[i];
	}
	solve(S);
	return 0;
}


Measured memory_bytes: 256