字节跳动2019年算法笔试题,你可以搞定吗?

2021-01-08 15:22:45 浏览数 (1)

点击上方蓝字,关注并星标,和我一起学技术。

大家好,这周我们继续来写一道招聘真题的题解。今天选择的题目来源于字节跳动2019年的春招笔试题,题目来源于牛客网,大家如果感兴趣可以去牛客网的题库当中实际参与。

我选的这一套题一共7题,这是其中的第三题,算是难度中规中矩的一题,比较考验基本功,很适合用来做笔试题。老规矩,我们废话不多说,先来看题意。

题意

小包最近迷上了一款叫做雀魂的麻将游戏,但是这个游戏规则太复杂,小包玩了几个月了还是输多赢少。

于是生气的小包根据游戏简化了一下规则发明了一种新的麻将,只留下一种花色,并且去除了一些特殊和牌方式(例如七对子等),具体的规则如下:

  1. 总共有36张牌,每张牌是1~9。每个数字4张牌。
  2. 你手里有其中的14张牌,如果这14张牌满足如下条件,即算作和牌
  • 14张牌中有2张相同数字的牌,称为雀头。
  • 除去上述2张牌,剩下12张牌可以组成4个顺子或刻子。顺子的意思是递增的连续3个数字牌(例如234,567等),刻子的意思是相同数字的3个数字牌(例如111,777)

例如:

1 1 1 2 2 2 6 6 6 7 7 7 9 9 可以组成1,2,6,7的4个刻子和9的雀头,可以和牌

1 1 1 1 2 2 3 3 5 6 7 7 8 9 用1做雀头,组123,123,567,789的四个顺子,可以和牌

1 1 1 2 2 2 3 3 3 5 6 7 7 9 无论用1 2 3 7哪个做雀头,都无法组成和牌的条件。

现在,小包从36张牌中抽取了13张牌,他想知道在剩下的23张牌中,再取一张牌,取到哪几种数字牌可以和牌。

输入描述:
代码语言:javascript复制
输入只有一行,包含13个数字,用空格分隔,每个数字在1~9之间,数据保证同种数字最多出现4次。
输出描述:
代码语言:javascript复制
输出同样是一行,包含1个或以上的数字。代表他再取到哪些牌可以和牌。若满足条件的有多种牌,请按从小到大的顺序输出。若没有满足条件的牌,请输出一个数字0

样例

输入例子1:
代码语言:javascript复制
1 1 1 2 2 2 5 5 5 6 6 6 9
输出例子1:
代码语言:javascript复制
9
例子说明1:
代码语言:javascript复制
可以组成1,2,6,7的4个刻子和9的雀头
输入例子2:
代码语言:javascript复制
1 1 1 1 2 2 3 3 5 6 7 8 9
输出例子2:
代码语言:javascript复制
4 7
例子说明2:
代码语言:javascript复制
用1做雀头,组123,123,567或456,789的四个顺子
输入例子3:
代码语言:javascript复制
1 1 1 2 2 2 3 3 3 5 7 7 9
输出例子3:
代码语言:javascript复制
0
例子说明3:
代码语言:javascript复制
来任何牌都无法和牌

题解

如果会打麻将的小伙伴看到这题应该很熟悉,这不就是麻将3x 2的胡牌规则么?

所谓的3x即是可以组在一起的三张牌,也就是题目中说的顺子和刻子。而 2,加的是两张一样的牌,对应题目当中的雀头。这题要做的就是给定现在摸到的牌型,然后给出听胡的牌。

我们很容易可以想到我们可以用一个长度为10的数组来存储现在已经摸到的牌,比如我们令这个数组叫做cards。cards[3]=2就说明我们有两张3,cards[6]=3就说明我们有3张6。这样把牌和对应的数量关联起来,我们就可以很方便地判断顺子和刻子的情况了。

存储搞定了之后,我们接下来要搞定的是胡牌以及听牌。其中听牌比较简单,我们只需要额外增加一张牌判断是否胡牌就可以了。比如说我们多摸了一张3之后,判断可以胡牌了,那么说明当前我们听的牌当中就有3。由于一共只有10张牌,我们只需要枚举一下可以听的牌即可。这里有一个小trick不小心很容易忽略,因为麻将当中一种牌最多只有4张,所以我们只需要考虑数量小于4的牌

胡牌的判断也很直观,可以分为雀头和刻子顺子两个部分。雀头好办,我们只需要枚举数量大于2的牌让它们成为雀头,判断剩下的牌能否全部组成顺子或者是刻子即可。全题唯一的难点就在这个组合的判断,虽然去掉雀头之后只有12张牌,也就是4个顺子或者是刻子,但由于顺子和刻子之间可以各种组合,尝试枚举是非常困难的,也是几乎不可能实现的。

如果看过我之前的文章,应该能反应过来,这里其实是一个经典的搜索问题。我们要枚举一下解是否成立,那么最好的办法就是尝试着把当前12张牌拆分成一个一个的顺子和刻子。如果能全部拆分完,那么就说明可以胡牌,否则则不行。搜索的情况只有两种,一种是组成顺子一种是组成刻子,倒是不复杂,但这里的代码却不好写。因为这是一个返回类型是bool型的深度优先搜索,对于新手而言可能相对比较陌生。

关于这个问题没有什么太好的办法,只有加大训练量以及多思考。一定要从递归的原理上去理解,只是把代码记下来似懂非懂是不行的,如果面试白板编程的时候碰到,肯定是会露馅的。

只要能反应过来这是一个搜索问题,并且对相关的代码还熟练的话,这题还是挺简单的,没有太多的trick和弯弯绕,数据也比较弱不用担心超时的问题。

代码语言:javascript复制
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <string>
#include <map>
#include <set>
#include <algorithm>
#include "time.h"
#include <functional>
#define rep(i,a,b) for (int i=a;i<b;i  )
#define Rep(i,a,b) for (int i=a;i>=b;i--)
#define foreach(e,x) for (__typeof(x.begin()) e=x.begin();e!=x.end();e  )
#define mid ((l r)>>1)
#define lson (k<<1)
#define rson (k<<1|1)
#define MEM(a,x) memset(a,x,sizeof a)
#define L ch[r][0]
#define R ch[r][1]
using namespace std;
const int N=1000050;
const long long Mod=1000000007;
int cards[14];

bool examine() {
    bool flag = true;
    // 如果全为0,说明已经拆分完了
    rep(i, 1, 10) if (cards[i]) {
        flag = false;
        break;
    }
    if (flag) return true;
    rep(i, 1, 10) {
        // 判断刻子
        if (cards[i] > 2) {
            cards[i] -= 3;
            flag |= examine();
            cards[i]  = 3;
        }
        // 判断顺子
        if (i < 8 && cards[i] && cards[i 1] && cards[i 2]) {
            cards[i] -= 1;
            cards[i 1] -= 1;
            cards[i 2] -= 1;
            flag |= examine();
            cards[i]  ;
            cards[i 1]  ;
            cards[i 2]  ;
        }
    }
    return flag;
}


int main() {
    rep(i, 0, 13) {
        int n;
        scanf("%d", &n);
        cards[n]   ;
    }
    vector<int> ret;
    // 枚举听牌
    rep(i, 1, 10) {
        if (cards[i] == 4) continue;
        cards[i]   ;
        // 枚举雀头
        rep(j, 1, 10) {
            if (cards[j] > 1) {
                cards[j] -= 2;
                // 递归
                if (examine()) {
                    ret.push_back(i);
                    cards[j]  = 2;
                    break;
                }
                cards[j]  = 2;
            }
        }
        cards[i] --;
    }
    if (ret.size() == 0) {
        puts("0");
        return 0;
    }
    sort(ret.begin(), ret.end());
    rep(i, 0, ret.size() - 1) {
        printf("%d ", ret[i]);
    }
    printf("%dn", ret[ret.size() - 1]);
    return 0;
}

为什么说这题很好呢,因为它考察的算法比较直白,也比较基础,即使没有参加过算法竞赛的同学也能做。但它又不是那么简单,可能很多同学能反应过来它是搜索问题,但是想要把这个dfs函数写正确,并且能在短时间内debug完通过还不是那么容易的。刚入门的新手估计能跑通就要半天,能够快速做出来并且通过一定是基础不错、代码功底比较扎实的同学。

好的问题并不一定就难,更重要的是区分度,可以把候选人的能力区分出来。大家如果准备面试做题练习的话,不妨从这个角度入手思考思考。

好了,今天的文章就到这里,衷心祝愿大家每天都有所收获。如果还喜欢今天的内容的话,请来一个三连支持吧~(点赞、在看、转发)

0 人点赞