阿里现在的面试这么难了吗?

2020-12-25 10:44:45 +08:00
 huang119412

内推,二面面试官约了两天后要笔试(之前听说内推没有笔试),我不敢大意,想到工作三四年了,应该不会考察特别难的算法。查了一下别人分享的面试,要不是就是没有笔试, 要不是就是一些线性表,多线程顺序执行问题。我的算法基础还行,曾经也刷过题,线性表问题(比如反转链表,合并有序数组之类)、二叉树问题( BST 等)、排序算法( TopK,Nth 等等)、 手写 BlockingQueue 、LRU 、还是多线程问题,我觉得这些对我来说没什么问题。

到了约定时间,面试官给我发了一个在线编程的网页,打开后题目已经在那里了,看起来是实际问题而不是具体的算法数据结构之类。面试官说给你 40 分钟,你把这两道题写完,我说能不能用 IDEA,面试官说不能,然后就不说话了。毫无代码提示补全,完全白板编程,不仅如此,题目描述都很简单,但是却有五六个类,之间都有关系,我抓紧认真审题,光弄懂题目都花了十多分钟,我吃惊的发现这两道题竟然都和动态规划有关,我当时心凉了一半。第一道题是用滑动窗口(双指针)找到极值,我用吃奶的力气才做完(白板编程,还有多层循环的 continue,break )。第二道题,经过我的抽象,发现竟然是一道复杂的背包问题。

题目大致是 n 个背包,m 个物品, 每个背包可以有某些物品任意件, 找出最少的背包包含所有的物品。 注: 此题一定有解。

    //经过我的抽象大致是这样的,重量和数量问题不用考虑
    public static class Product{}
    public static class Package{}
    //此物品是否在包裹中
    public boolean productInPackage(Package packet, Product product) { ***** }

    //完成此方法: 每个背包可以有某些物品任意件,找出最少的背包包含所有的物品
    public Map<Product, Package> findLeastPackages(List<Product> products, List<Package> packages) {}

心里真的拔凉拔凉的。时间到了我第二题只做到一半(找到了所有背包中所有的物品),后面时间不够,集合的交叉并补也记得不是很清楚了。而且只有大致的思路。也没想到最优的解法。

我 JDK 重要源码学了一遍又一半,HotSpot 源码也有所涉猎,研究 JDK 的 BlockingQueue 、ConcurrentLinkedQueue 、WorkStealingQueue,JCtools 的 SPSC 、MPSC 、MPMC,Disruptor RingBuffer, 学习各种 lock-free 算法和结构,心想自己技术水平还算可以,没想到折戟在这里。

不知道是不是内推的这个部门不招人( JD 描述还是 9 月份),自己一直对阿里有好感,但是一面面试官的傲慢,二面出这种题目白板编程,感觉自己被耍了。我只是面一个 P6 而已,现在也是公司的一个技术小 leader,每天 5 点多就能下班了,笔试晚上 9 点半还能听到对面的人在讨论需求。说实话这些对我有影响,但不是最重要的,我想去阿里因为我对自己和技术还有追求。当然最想去阿里中间件团队,但是据说特别难,所以选择了曲线救国的方法。可是遭遇这一遭。

自己的解法,笔试结束后用 IDEA 花了近 2 个小时才写完,又花了一些时间优化了代码,但是不知道还有什么简单或最优的解法。


    public static class Product{}
    public static class Package{}
    public boolean productInPackage(Package packet, Product product) {}

    // n 个背包, m 个物品, 每个背包可以有某些物品任意件, 找出最少的背包包含所有的物品  注: 此题一定有解
    //不考虑背包的权重、背包中物品权重、物品数量和重量
    public Map<Product, Package> findLeastPackages(List<Product> products, List<Package> packages) {

        if (products == null || products.isEmpty() || packages == null || packages.isEmpty()) {
            return null;
        }

        Set<Product> productSet = new HashSet<>(products);
        Set<Package> packageSet = new HashSet<>(packages);

        int productsSize = productSet.size();
        int packagesSize = packageSet.size();

        //返回值
        Map<Product, Package> priorityPackages = new HashMap<>(productsSize);

        //包裹 <=> 包裹里物品的双向对应
        //可以使用 Guava 的 Bimap
        Map<Package, Set<Product>> allPackages = new HashMap<>(packagesSize);
        Map<Set<Product>, Package> productSetPackage = new HashMap<>(packagesSize);

        //寻找到包含数量物品种类最大的包裹
        Package maxProductsPackage = null;
        Set<Product> productTempSet = null;

        for (Package aPackage : packageSet) {

            if (aPackage == null) {
                continue;
            }

            //初始化 aPackage
            allPackages.put(aPackage, (productTempSet = new HashSet<>()));
            productSetPackage.put(productTempSet, aPackage);

            for (Product product : productSet) {
                if (product == null) {
                    continue;
                }

                //物品在背包中, 放入背包
                if (productInPackage(aPackage, product)) {
                    allPackages.get(aPackage).add(product);
                }
            }

            if (maxProductsPackage == null) {
                maxProductsPackage = aPackage;
            } else {
                maxProductsPackage = allPackages.get(aPackage).size() >= allPackages.get(maxProductsPackage).size() ? aPackage : maxProductsPackage;
            }
        }

        //已经找到背包有哪些物品
        //开始集合运算

        //maxProductsPackage 种类最多, 说明这个一定是最优里面的
        //maxProductsPackage 包含所有种类 直接返回
        if (allPackages.get(maxProductsPackage).size() == productSet.size()) {
            for (Product product : productSet) {
                priorityPackages.put(product, maxProductsPackage);
            }

            return priorityPackages;
        }

        //todo 机试的就写道这里  主要记不太清楚集合的交叉并补 API,时间也不足  (40 分钟白板写代码)
        //没有使用 lambda 、Stream API 主要是记忆问题(白板写代码), 还有通过数组包装局部变量, 还有多层循环 break


        // 1.删除 maxProductsPackage 子集的包裹
        // 2.找到其他包裹和 maxProductsPackage 差值最大的包裹, 并取并集作为新的 maxProductsPackage
        // 3.判断 maxProductsPackage 是否包含所有物品, 是的话返回, 不是的话重复第一步直到找到结果或集合为空(一定有答案)

        Set<Product> maxProducts = allPackages.get(maxProductsPackage);
        Set<Product> secondMaxProducts = null;

        //删除最大包裹
        allPackages.remove(maxProductsPackage);

        //留下来的包裹 [不在最大包裹之内, 有差值, 但不是差值最大的]  找到差值最大的合并到 maxProducts, 然后转移到 mergeSets
        HashSet<Set<Product>> remainSets = new HashSet<>(allPackages.values());

        //和最大包裹差值最大的, 已经合并到最大包裹内 [结果一定在这个里面]
        Set<Set<Product>> mergeSets = new HashSet<>(packagesSize);
        mergeSets.add(maxProducts);

        while (maxProducts.size() != productsSize) {

            //如果 remainSets 为空,且 maxProducts.size() != productsSize 说明没有答案[不会发生]
            //可以把所有包裹相加去重后如果!= productsSize, 说明没有答案, 这样可以更快排除,这里只是以防万一
            if (remainSets.isEmpty()) {
                return null;
            }

            //寻找次大的包裹, 不需要比较优先级 [使用 iterator 模式删除元素, 优化循环]
            Iterator<Set<Product>> iterator = remainSets.iterator();

            while (iterator.hasNext()) {

                Set<Product> next = iterator.next();
                next.removeAll(maxProducts);

                //是 maxProducts 的子集
                if (next.isEmpty()) {
                    iterator.remove();
                    continue;
                }

                //初始化 secondMaxProducts    可以删除次大元素减小集合
                if (secondMaxProducts == null) {
                    secondMaxProducts = next;
                } else {
                    secondMaxProducts = next.size() > secondMaxProducts.size() ? next : secondMaxProducts;
                }
            }

            // 已经找完,退出循环
            if (secondMaxProducts == null || secondMaxProducts.size() == 0) {
                break;
            }

            // 把 secondMaxProducts 加入到 maxProducts
            ////更新 maxProducts
            maxProducts.addAll(secondMaxProducts);

            //更新 mergeSets
            mergeSets.add(secondMaxProducts);

            //删除此元素
            remainSets.remove(secondMaxProducts);
            secondMaxProducts = null;
        }

        //mergeSets 即为所求
        mergeSets.forEach(set -> set.forEach(product -> priorityPackages.put(product, productSetPackage.get(set))));
        return priorityPackages;
    }
24174 次点击
所在节点    程序员
116 条回复
Suddoo
2020-12-25 14:00:19 +08:00
面试官应该会跟你交流的吧,题目意思应该尽量澄清,毕竟面试官也不想浪费时间在理解题意上。之前面的两家面试官都会解释题目意思,交流的过程中思路不对就直接提示不要往这个方向想了,实在没有思路也会给一点提示。

阿里不清楚啊,之前面了几个部门,感觉就是背八股文 + 项目,最后让我手写一个快排发面试官邮箱(感觉就是走个形式),因为我一直做的是打杂的项目,也没啥好说的,蚂蚁三面也没问啥技术,问了很多人生方面的,最后就挂了
Suddoo
2020-12-25 14:02:40 +08:00
@binux 是的,我面试的时候举了一些 testcase,结果被面试官指出 testcase 太特殊了,结论是错误,交流很重要,列举 testcase 的过程也可以加深自己对题目的理解
leoli
2020-12-25 14:07:25 +08:00
本人 985 本硕,工作近 10 年。表示面试阿里面不过去,我深深的感觉自己很挫!
christin
2020-12-25 14:22:53 +08:00
@tcfenix 那个哥们说的是 6 轮 1 面算法 后面 bo5
callmexiaodeng
2020-12-25 14:30:52 +08:00
来亚马逊试一下嘛
mxT52CRuqR6o5
2020-12-25 14:31:58 +08:00
想要最优解的话需要动态规划相关的知识,这种应该是把思路讲明白就好了
如果不动态规划的话,就要用一些效率不是最高的暴力解法了
USAA
2020-12-25 14:35:55 +08:00
@berg223

可以啊,你最开始循环的时候,就已经开始用临时变量计数了,后面直接取出来判断啊
BIAOXYZ
2020-12-25 14:46:39 +08:00
@leoli #43 老哥不用太在意,面试这个跟很多因素都有关系(当然最重要的还是眼缘)。比如 lz 碰到这种手撕两道 hard,那一般社招的人真的过不去。
laminux29
2020-12-25 14:51:39 +08:00
作为带过的徒弟已经在 bat 带队的大佬,出来讲几句。

1.面试,这本来就是一场对被面试者很不公平的事情,因为 IT 整个技能树,知识点犹如浩瀚大海,正常的个人根本没办法完全覆盖所有知识点。面试官如果真想为难我,他完全可以去 mysqll 领域,随便找个版本,来问我某个模块有啥 bug 。我就算是一千年前开始学计算机,也没办法百分百完美地回答上这个问题。

2.面试官的态度、面试的难度、面试成功率等等问题,牵涉到太多因素,比如职员数量的供求关系、企业经营现况、面试官个人职业素养等等。假设你是面试官,甚至公司老板,想想你在招人时,需要考虑多少因素与条件。因此,面试无论输赢,都不用太激动或太沮丧。很多时候走了顺电梯流或逆电梯流,在面试者没研究过公司经营,没有了解整个行业以及国家的运营情况,根本不知道自己面试成功或失败的关键原因。这就像是向一个女生表白,被拒绝的时候,除非开挂,否则你很难猜对对方拒绝你的真正原因。

3.加入一个组织,最好的姿态是,组织需要做某个新领域,没有经验,然后你又是该领域的大佬或小佬,这种情况加入进去,对双方都是最合适最舒服的。

然而,题主在一个公司走下坡路,面试官职业素养又不高,而且要进去的领域已经充分竞争,这种情况下,去面试,能顺利都已经谢天谢地了,被各种嘲讽或被为难必然是正常状态。
zkywalker
2020-12-25 15:07:07 +08:00
@laminux29 这位大佬说的非常对,面试就是一场信息不对称的考试。面试官可以通过你的简历去挖掘你的工作细节,也可以根据自己的经历和职位要求去考察你的知识储备。但是你这种笔试感觉不是面对社招的,社招出这东西大概率是不缺人。。。
lewis89
2020-12-25 15:08:41 +08:00
@laminux29 #49 老哥 说的真的很在理,我觉得面试最好还是去挖掘对方的优点,IT 知识确实浩瀚大海,我读完 CSAPP 再看一些老程序员在 386 栈上玩,那个时候的协程 连自己的栈都没有,都是自己用 sp 指针 开几个空间 让协程去玩,玩完了再返回回来,要是问这种计算机考古学,我也答不上来,我现在基本上是先问下最近看了什么书,然后大概讲一下书上讲了哪些概念跟模型,以及面试者自己的一些理解。
leoli
2020-12-25 15:10:41 +08:00
@BIAOXYZ 说实话手撕 hard 我真不怕,阿里那帮人老是揪着些八股文问来问去的,上次问了个什么细节来着,我说我忘了,他们说那看来你也不怎么样嘛。哎,从此之后阿里再打过来电话直接不甩他们了。
xrr2016
2020-12-25 15:12:42 +08:00
去面微软🐶
bibizhang
2020-12-25 15:24:51 +08:00
真难,我不是开发我都看不懂。
berg223
2020-12-25 15:28:00 +08:00
@USAA 抱歉,我之前想了一下贪心算法的反例,看到你的评论,以为我们想法相同,所以留下一个反例在这。但仔细看了下你的评论,发现咱俩理解的题意不一样。区别在于你理解的题意是每个背包含有好几种物品,每种物品还会有若干件,需要凑齐指定数量的某几种物品(比如你要凑齐 A 物件 3 件,B 物件 5 件);我理解的题意是每个背包只是含有好几种物品,只需要凑齐某几类物品即可,所以是你的一个子问题。我的反例是针对这个子问题的。可以从这个子问题的反例推导出你的反例:
有 4 个背包,总共有 6 种物品(用编号 1~6 来表示),每类物品都需要 1 件
背包 1 含有 1,2,3 这三类物品,每种物品都是 1 件;
背包 2 含有 1,4 这三类物品,每种物品都是 1 件;
背包 3 含有 2,5 这三类物品,每种物品都是 1 件;
背包 4 含有 3,6 这三类物品,每种物品都是 1 件;
按照你的解法,因为它的物品个数最多,会选择第一个背包,但是最优的组合是选取 2,3,4 这三个背包,所以是一个反例。
IMCA1024
2020-12-25 15:30:23 +08:00
楼主加油 不错了的
YouLMAO
2020-12-25 15:37:14 +08:00
楼主,我司不开发 jvm 的, 你学了白学了, Spring Cloud Alibaba 明天我出题看看你
lewis89
2020-12-25 15:40:42 +08:00
@zkywalker #50 没办法,现在大厂都是这么一个套路,必须要刷题,不刷题必死,风气就是这么带起来的,我每次面完算法都是头疼
Cbdy
2020-12-25 15:46:07 +08:00
看了一下感觉常规题吧,别用 Java 写,罗里罗嗦写起来慢,用 IDE 都勉强,遑论白板
BiteTheDust
2020-12-25 15:49:48 +08:00
这基本就是模拟题了吧 似乎几乎没有涉及到任何算法的样子.....

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/738815

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX