麻将牌和牌问题
在知乎上看到一個問題,如何用程序判斷麻將牌是否和牌。和牌的規則為:14張麻將牌當中,必須要有一個對子,即兩張相同的牌,除去對子后,剩下的牌可以組合成”A, A, A”或者”A, A+1, A+2”兩種模式的組合,其中A為某種花色的某張牌。
我的解法是:
首先定義一下麻將牌在程序中的表示方式:
把萬,餅,條分別用連續的數字代表,比如:一萬到九萬用1到9表示,一餅到九餅用11到19表示,一條到九條用21到29表示。重要的是每一種牌內部數字是連續的,不同牌之間的數字不連續。對于風和中發白,每一種都用互相之間不連續的數字表示,比如中是31,發是33,白是35等等。
由于和牌中必需要有一個對子,所以我們的思路是,去除可能的對子,然后判斷剩下的牌是否可以表示成“A A A”或者“A A+1 A+2 ”這兩種模式的組合。
具體流程是:對輸入的14張麻將牌序列進行排序,排序后,由低到高依次尋找對子,如果隊列中沒有對子,那么肯定不是和牌,程序結束。找到對子后,將這兩張對子從序列中刪除,判斷其余的牌是否滿足下面的條件,如果滿足,則為和牌,結束,如果不滿足,則在排序后的輸入隊列中尋找下一個對子,重復本過程,直到條件1滿足或者搜索到序列結尾。
條件1:序列中的元素可以分為每3個元素一組,每組元素均為“A A A”或者“A A+1 A+2 ”的形式。
為了判斷一個序列是否滿足條件1,我們觀察到以下幾個性質:
性質1:排序后的麻將牌序列可以分割成若干子序列,其中每個子序列內部的元素是連續的,且不同序列間的元素不連續(連續指相鄰元素的值相同或值相差1)。
性質2:排序后的麻將牌序列滿足條件1的充分必要條件是,按性質1分割的每個子序列均滿足條件1。
性質3:如果一個序列滿足條件1,則按性質1分割后的子序列元素個數必定為3的整數倍。
于是,我們下一步要做的事情就是,將排序后的序列按照連續性分組,然后遞歸判斷每組是否滿足條件1,如果所有組都滿足條件1,則分組前序列也滿足條件1,否則不滿足。
為了判斷一個連續序列是否滿足條件1,可以分幾種情況考慮:
1. 如果序列中有3個元素,則直接判斷是否屬于條件1中的兩種情況即可;
否則,設A為序列中的最小值,
2. 如果A的個數大于等于3(即3或4),則序列滿足條件1的充分必要條件是序列去掉3個A后的剩余元素滿足條件1;
3. 如果A的個數小于3(即1或2),則序列滿足條件1的充分必要條件是序列去掉(A, A+1, A+2)后的剩余元素滿足條件1;
時間復雜度:
由于牌的個數是常數14,且每輪迭代數據規模都會減小,因此,該算法時間復雜度為O(1)。
代碼:
#include <iostream> #include <vector> #include <algorithm>using namespace std;static vector<int> result;//for debug void prtvct(const vector<int> &v, bool nxtline = true) {for (auto it = v.begin(); it != v.end(); ++it) {cout << *it << " ";}if (nxtline)cout << endl; }bool erase_by_val(vector<int> &in, int val) {for (auto it = in.begin(); it != in.end(); ++it) {if (*it == val) {in.erase(it);return true;}}return false; }void push_result(int a, int b, int c) {result.push_back(a);result.push_back(b);result.push_back(c); }bool shrink(vector<int> &in) {if (in[0] == in[1] && in[1] == in[2]) {push_result(in[0], in[1], in[2]);auto it = in.erase(in.begin());it = in.erase(it);in.erase(it);return true;} else {if (in[in.size()-1] - in[0] < 2) {return false;}}int i, t;for (i = 0, t = in[0]; i < 3; ++i, ++t) {erase_by_val(in, t);}push_result(t-3, t-2, t-1);return true; }void group(vector<int> &input, vector<vector<int> > &grp) {vector<int> member;if (input.begin() != input.end()) {member.push_back(input[0]);} else {return;}for (auto it = input.begin()+1; it != input.end(); ++it) {if (*it == *(it-1) || *it == *(it-1)+1) {member.push_back(*it);continue;}grp.push_back(member);member.clear();member.push_back(*it);}grp.push_back(member); }bool isLegal(vector<int> &input) {vector<vector<int> > grp;if (input.size() % 3 != 0) {return false;}if (input.size() == 3) {if (input[0] == input[1] && input[1] == input[2]) {push_result(input[0], input[1], input[2]);return true;}if (input[1] == input[0] + 1 && input[2] == input[1] + 1) {push_result(input[0], input[1], input[2]);return true;}return false;}group(input, grp);// for each groupfor (auto it = grp.begin(); it != grp.end(); ++it) {if (shrink(*it) == false) {return false;}if (isLegal(*it) == false) {return false;}}return true; }bool isHu(vector<int> &input) {sort(input.begin(), input.end());int i = 0;while (i < input.size()-1) {result.clear();auto incopy = input;if (input[i] == input[i+1]) {result.push_back(input[i]);result.push_back(input[i+1]);auto it = incopy.erase(incopy.begin() + i);incopy.erase(it);// processif (isLegal(incopy) == true) {return true;}}do { ++i; } while (i < input.size()-1 && input[i] == input[i-1]);}return false; }int main() {int n = 0;vector<int> input;cin >> n;for (int i = 0; i < n; ++i) {int a;input.clear();for (int j = 0; j < 14; ++j) {cin >> a;input.push_back(a);}if (isHu(input)) {cout << "Hu: ";prtvct(result);} else {cout << "Not Hu" << endl;}}return 0; }總結
- 上一篇: 首个中文全词类知识库-百科知识树 开源啦
- 下一篇: 【NOWCODE SEVEN】:二分查找