程序员老鬼

5B的同学吐槽说,他们HR每天像守株待兔一样盯着监控。有人刚离开工位,系统还没反应,HR已经开始记录离岗次数了。

刚看到个贴子,说5B有同学爆料,他们HR每天守着监控,谁屁股刚离开椅子,人还在走廊,旁边就已经在记“离岗次数”了。

Image

有人在评论区说,这种多半就是为了扣钱,楼主只回了俩字:恶心。

我觉得这事吧,问题根本不在那几分钟,而在公司完全不信任员工。监控本来是为了安全,现在变成电子项圈,大家心里只会想:少动、多装样、别被抓。表面上工位一个不落,实际效率反而更低。

换个角度说,企业怕摸鱼可以理解,但与其盯人,不如盯结果:目标讲清楚,活儿交代明白,完不成再谈责任。把员工当人看,才有人愿意替你拼;天天当嫌疑人查,最后留下的,只会是混一天算一天的

面试题:每位顾客最经常订购的商品

想象下啊,你在做一个外卖后台系统,运营同事突然跑过来问一句: “能不能给我算一下,每个用户最经常点的是啥?我要给他们发个‘常点菜’优惠券。”

这题就来了:每位顾客最经常订购的商品。 听着挺像业务需求对吧,其实就是一个很典型的统计类算法题。

用最朴素的话说,就是有一堆订单记录,每条包含:

  • customerId:顾客是谁
  • productId:点的什么东西

现在要算:对每个 customerId,找出那个被他点次数最多的 productId。

有几个小坑一般会顺带问你:

  • 如果一个顾客有两个商品点的次数一样多,怎么办?随便返回一个?按商品 id 最小的?
  • 没下过单的顾客要不要出现在结果里?一般不用。

面试的时候最好顺嘴问一句,不问就自己约定好,在代码里写清楚注释。

脑子里可以先过一遍整个流程,别一上来就是“HashMap 套 HashMap”。

大概就是两步:

  1. 先统计:对每个顾客,他点每个商品的次数是多少。
  2. 再比较:对同一个顾客,把他点过的商品次数拿出来,挑个最多的。

翻译成数据结构就是:

  • 外层:Map<String, Map<String, Integer>>

    • key:customerId
    • value:这个顾客点过的所有商品及次数 Map<productId, count>

做统计的时候,每看到一条 (customerId, productId):

  • 先在外层 map 里找到这个顾客对应的内层 map,没有就 new 一个
  • 再在内层 map 里把这个商品的 count + 1

统计完了,再对每个顾客的内层 map 遍历一遍,找最大值,就得到“最经常订购的商品”。

时间、空间都挺正常的,别怕。

先假设有这么个简单的订单类:

publicclassOrder{
private String customerId;
private String productId;

publicOrder(String customerId, String productId){
this.customerId = customerId;
this.productId = productId;
    }

public String getCustomerId(){
return customerId;
    }

public String getProductId(){
return productId;
    }
}

核心方法,可以写成这样:

import java.util.HashMap;
import java.util.List;
import java.util.Map;

publicclassMostFrequentProduct{

/**
     * 统计每个顾客最常点的商品
     * 返回:key = customerId, value = 该顾客最常点的 productId
     */

publicstatic Map<String, String> getMostFrequentProduct(List<Order> orders){
// customerId -> (productId -> count)
        Map<String, Map<String, Integer>> countMap = new HashMap<>();

// 1. 先把次数统计出来
for (Order order : orders) {
if (order == null) {
continue;
            }
            String customerId = order.getCustomerId();
            String productId  = order.getProductId();
if (customerId == null || productId == null) {
continue; // 简单防一下脏数据
            }

            Map<String, Integer> productCount =
                    countMap.computeIfAbsent(customerId, k -> new HashMap<>());

int newCount = productCount.getOrDefault(productId, 0) + 1;
            productCount.put(productId, newCount);
        }

// 2. 对每个顾客,找出次数最多的商品
        Map<String, String> result = new HashMap<>();

for (Map.Entry<String, Map<String, Integer>> entry : countMap.entrySet()) {
            String customerId = entry.getKey();
            Map<String, Integer> productCount = entry.getValue();

            String bestProduct = null;
int maxCount = 0;

for (Map.Entry<String, Integer> pc : productCount.entrySet()) {
                String productId = pc.getKey();
int cnt = pc.getValue();

// 这里顺带处理并列情况:次数相同,就按 productId 的字典序更小的
if (cnt > maxCount ||
                        (cnt == maxCount && bestProduct != null && productId.compareTo(bestProduct) < 0)) {
                    maxCount = cnt;
                    bestProduct = productId;
                }
            }

// 理论上不会是 null,这里防守式写法
if (bestProduct != null) {
                result.put(customerId, bestProduct);
            }
        }

return result;
    }
}

大体意思:

  • 一遍循环把所有次数算出来
  • 再一遍循环把每个人的“第一名商品”挑出来
  • 并列的时候,我这里的规则是:次数相同就取字典序更小的 productId,这样行为是确定的,写在注释里就行

假设订单总数是 N,顾客数是 U,商品数是 P(这俩只是帮你理解的,其实不重要):

  • 统计那一遍:扫描订单列表,一次遍历,时间是 O(N)
  • 对每个顾客找最多次数的商品:最坏情况每个人点过所有商品,也是 O(N) 级别

整体时间复杂度就是 O(N), 空间上存了所有计数,大概也是 O(N) 级别,这个说出来就够用了。

面试或者写业务的时候,只要你能把这个套路在脑子里过一遍,落到 Java 上基本就是 Map 套 Map,再加一点点细节处理,就差不多了。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

最后给大家分享一份不错的副业资料,点击下方公众号,回复关键字: 副业 领,也可以链接我微信:hls404