麻将牌和牌问题

在知乎上看到一个问题,如何用程序判断麻将牌是否和牌。和牌的规则为: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 group
        for (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);

                        // process
                        if (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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值