今日头条:麻将是否可以胡牌

398 阅读3分钟

题目

有一个同样的两张牌做将,然后剩下的组成ABC或者AAA的形式。假设每种有13张牌,都是1到9,共四种牌的类型

算法

package toutiao;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

/**
 * 麻将游戏
 *
 * @author zyq
 */
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String string = scanner.next();
        String[] s = string.split(" ");
        int[] arr = new int[13];
        int index = 0;
        for (String temp: s) {
            arr[index]=Integer.parseInt(temp);
            index++;
        }
        Main game = new Main();
        // 可能有两组将的情况下
//        int[] arr = {1, 1, 1, 2, 2, 2, 5, 5, 5, 6, 6, 6, 9};
//        int[] arr = {1, 1, 1, 2, 2, 2, 3, 3, 3, 5,6, 7, 7, 9};
//        int[] arr = {1, 1, 1, 1, 3, 3, 2, 2, 5, 6, 7, 8, 9};
//        int[] arr = {1, 1, 1, 1, 2, 2, 3, 3, 5, 6, 7, 8, 9};
        game.detectHu(arr);
    }

    /**
     * 校验是否可以胡牌,并且输出所有胡牌情况下将和听的牌。13张牌的数组,假设都是万(如果包括其他的花色其实道理一样)。胡牌的规则为:有一个同样的两张牌做将,然后剩下的组成ABC或者AAA的形式。
     *
     * @param arr
     */
    private void detectHu(int[] arr) {
        if (arr == null || arr.length != 13) {
            System.out.println("Illegal array for mahjong game!");
            return;
        }

        // 定义一个新的数组,存储每一个万出现的次数
        int[] countArr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            countArr[arr[i]]++;
        }

        // 我们的算法思路是:先依次假设每一个牌作为将,剩下牌的再去判断是否满足ABC的形式。
        List<Hu> possibleHu = new ArrayList<Hu>();    // 存储胡的结果列表
        int[] newArrOne = new int[countArr.length];
        ArrayList<Integer> res = new ArrayList<>();
        for (int i = 1; i <= 9; i++) {
            if (countArr[i] == 1) {
                System.arraycopy(countArr, 0, newArrOne, 0, newArrOne.length);
                newArrOne[i] = newArrOne[i] - 1;
                if (isABCOrAAAPattern(newArrOne)) {
                    res.add(i);
                }

            }
        }


        for (int i = 1; i <= 9; i++) {
            // 如果对应的万字牌个数小于两个,则不可能做将。这里直接忽略。
            if (countArr[i] < 2) {
                continue;
            }

            // 用一个全新的数组,判断数组中的数是否都满足ABC的形式。
            int[] newArr = new int[countArr.length];
            List<Integer> tings = new ArrayList<Integer>();

            // 我们可以假设每次听的不同的万字牌,将要胡的万字牌加进去之后,如果剩下的牌满足AAA或者ABC模式,则证明可以听该万字牌
            for (int j = 1; j <= 9; j++) {
                System.arraycopy(countArr, 0, newArr, 0, newArr.length);
                newArr[i] -= 2;            // 对应做将的万字个数减2
                if (j == i && newArr[j] + 1 > 2) {    // 不能超过4个牌
                    continue;
                } else {
                    if (newArr[j] + 1 > 4) {    // 不能超过4个牌
                        continue;
                    }
                    newArr[j] += 1;        // 假设听该万字牌
                }

                if (isABCOrAAAPattern(newArr)) {
                    tings.add(j);
                }
            }

            // 当以i为将的时候存在可以听得牌,则证明该将可以胡。将其存入结果列表
            if (!tings.isEmpty()) {
                Hu newOne = new Hu();
                newOne.jiang = i;
                newOne.tings = tings;
                possibleHu.add(newOne);
            }

        }

        if (possibleHu.isEmpty() && res.isEmpty()) {
            System.out.println("0");
        } else {

            for (Hu one : possibleHu) {
                res.addAll(one.tings);
            }
            Collections.sort(res);
            for (int i = 0; i < res.size(); i++) {
                System.out.print(res.get(i) + " ");
            }
        }
    }

    /**
     * 校验数组是否满足ABC形式
     *
     * @param newArr
     */
    private boolean isABCOrAAAPattern(int[] newArr) {
        for (int i = 0; i < newArr.length - 2; i++) {
            // 一旦有3张的情况,必然只能是AAA类型的,其他情况都是ABC类型的
            while (newArr[i] > 0 && newArr[i] != 3) {
                newArr[i] -= 1;
                if (newArr[i + 1] == 0) {
                    return false;
                } else {
                    newArr[i + 1] -= 1;
                }

                if (newArr[i + 2] == 0) {
                    return false;
                } else {
                    newArr[i + 2] -= 1;
                }
            }
        }

        // 剩下的如果每个万字牌的个数为0或者3。才表示可以听此牌。
        for (int i = 0; i < newArr.length; i++) {
            if (newArr[i] != 0 && newArr[i] != 3) {
                return false;
            }
        }

        return true;
    }

    /**
     * 定义胡牌的情况,将和听的牌
     *
     * @author zyq
     */
    private class Hu {
        private int jiang;
        private List<Integer> tings;
        // 可能听的牌不止一张
    }
}

参考

麻将胡牌判定的问题