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

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;
    }
24127 次点击
所在节点    程序员
116 条回复
reedthink
2020-12-25 11:20:59 +08:00
背包问题,搭配《挑战程序设计竞赛》和背包九讲,基本所有背包题都能切掉
wdytoya
2020-12-25 11:22:01 +08:00
只能说哥们没遇到匹配的面试官吧,那个白板编程系统我就不喜欢用,我宁愿发题目让候选人离线做看能做到最好的是什么程度,既能看优化,又能看代码风格。不过我们一般一面可选加笔试,二面往后一般就不会再上笔试了,你这遇到的团队也是有点奇怪

ps.看你似乎对中间件更感兴趣,不过我们一般讲技术都是要服务于业务的,如果你对业务团队感兴趣的话,我这边是支付宝用户增长团队,欢迎投递简历到 dongyang.wdy@alipay.com
woshiaha
2020-12-25 11:22:56 +08:00
据说是今年开始阿里对齐字节 面试要求必须考察算法题 不过我感觉不同阿里面试官面试的浮动很大
大部分一面二面随便问问基础然后留道比如 LRU 淘汰或者链表题目就结束了
或者你面的 BU 特别核心?
实际上同一个面试官前后面试力度都有可能不同。。。
部门刚组建急招人就疯狂放水 到了 HC 快没了就开始各种刁难
要不咋说面试起码五分靠运气
aureole999
2020-12-25 11:30:19 +08:00
基本等于这题?
https://leetcode.com/problems/smallest-sufficient-team/
直接想不出 dp 感觉先写个 bfs 也行,尤其是时间给的又不多。
hehe12980
2020-12-25 11:32:45 +08:00
算法 写的太复杂了 为啥要抽象呢== 没弄懂 直接拿几个数组 去表示不行么 可能是我 leet 刷多了
huang119412
2020-12-25 11:35:40 +08:00
@hehe12980 这是实际问题,我只是脱敏而已
USAA
2020-12-25 11:38:27 +08:00
我这么想的,当然我没刷题哈。
如果有不对的,希望大佬指正

n 个背包,m 个物品, 每个背包可以有某些物品任意件, 找出最少的背包包含所有的物品。

先找出每个背包包含物品的种类和个数
弄个数组吧,背包[全部种类个数,[ [种类 A,个数],[种类 B,个数].....] ]
然后 for 循环取个数最多,每个种类个数最小。。。。。。。。。
kera0a
2020-12-25 11:39:44 +08:00
@tcfenix 昨天有个帖看到的,过了一面后是 BO5 。一面没过才是一轮游吧,我觉得挺好的
huang119412
2020-12-25 11:39:55 +08:00
@aureole999 我不是没想到怎么做,只是没想到最好的方法,还有方法。看一下这个方法的返回值和参数,比您发的这道题复杂一些,背包的物品有哪些东西都要自己先统计。
tcfenix
2020-12-25 11:51:09 +08:00
@woshiaha
之前没有强制要求考算法的时候就根本招不到什么人....现在还强制要求考算法....真是自信额
wagjia
2020-12-25 11:53:30 +08:00
阿里巴巴最近不是被约谈了???
aureole999
2020-12-25 11:55:06 +08:00
@huang119412 国外公司的白板一般不需要直接写出最优方法,可以边写边讨论再改进,主要是考察思维过程。埋头猛写不说话的那种可能写出最优方法没 Bug 也不一定能拿到好评价。方法名参数忘记啥的跟面试官解释一下输入和输出就行。至于阿里面试是不是也是这样,我就不知道了。你也没说你中间有没有跟面试官讨论。光看你的描述面试确实挺变态的。
se77en
2020-12-25 12:00:08 +08:00
@wagjia 大厂且国家队持股才有资格被约谈,小厂直接搞死...
berg223
2020-12-25 12:00:28 +08:00
@USAA 我觉得这个解法有问题吧,举个反例
有 4 个背包,6 种物品,每个背包包含的物品分别是:
1,2,3 ;
1,4 ;
2,5 ;
3,6 ;
很明显最优的选择不会选择 1,2,3 这个背包。
@hehe12980 题目描述中的 n 和 m 最大是多少?
binux
2020-12-25 12:14:38 +08:00
题目不全,在不给出数据范围的时候不好说最优解,但是至少有两点你可以在下次面试的注意一下。

> 题目描述都很简单,但是却有五六个类,之间都有关系,我抓紧认真审题,光弄懂题目都花了十多分钟

首先,这是一场面试,交流能力是面试中很重要的一环。无论题目是否清晰,你都应该和面试官交流,确认题意,数据范围,test/edge case 举例,阐述你对题目的理解,已经你准备使用的算法。
如果你和面试官交流了 10 分钟,你不应该沮丧,反而这是很好的,有意义的。

> 发现这两道题竟然都和动态规划有关
> 用吃奶的力气才做完
你误解了算法面试的意义,算法面试并不要求你“做完”,也不要求“最优解”(当然,能找到最优解更好)。和上面说的一样,面试最重要的是交流。
首先是“做完”,不知道你有没有事先和面试官确认你的想法,然后再实际编码。一般算法面试的代码量不会很大,如果你能写出上百行,那么可能是做法有问题。不过由于题目不全,或许真就很复杂呢...
然后是“最优解”,在你无法一步到位的情况下,你可以使用 brute force 。面试官会根据他对这个职位的期望,引导你寻找更优解,或者将最优解的算法作为你完成 brute force 之后的 follow up 。

总之算法面试关键在于面试而不是算法。调整好心态,别太纠结,多刷刷题,下次就好了。
HytonightYX
2020-12-25 12:46:24 +08:00
@binux 说的非常 nice,同感
rodrick
2020-12-25 13:06:02 +08:00
心态放好吧,我觉得不能说是面试难,可能是你的准备正好不在面试官面的点上,看你源码各种准备了很多,算法可能不够充足但是也准备了一些,偏偏没考到你准备的,也算是运气吧,现在面试和上学考试一样,总会碰到你不会的大题,也别气馁。说句不好听的,阿里约谈了还是阿里,有这种所谓的不平等也算是扭曲的“正常”,总而言之不算是你的问题,再加油准备准备吧
e583409
2020-12-25 13:18:18 +08:00
人生苦短 默默的安慰一下
unijiang
2020-12-25 13:36:53 +08:00
楼主微软欢迎你,有兴趣私信哈
AlexWIT
2020-12-25 13:55:51 +08:00
@binux 道理确实是这么个道理,但我从他描述的情况来看面试官好像不太愿意交流,把题目一扔让面试者自己写完就完事了,这种情况还能跟面试官确认想法吗?

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

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

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

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

© 2021 V2EX