刚看到一个帖子,讲的是一次略显意外的面试经历。一位网友面试了一位45岁的程序员,对方期望月薪是两万,面试官觉得人合适,当场就同意了。结果送对方到电梯口时,这位候选人补了一句:“如果公司能提供14薪的话,月薪一万八也行。”

图1:脉脉社区关于45岁程序员面试谈薪的讨论截图
帖子内容很简单,核心就是一次临门一脚时的薪资谈判。评论区观点分成两派,有人说这位程序员太会算计,步步试探底线;也有人表示理解,认为中年程序员考虑更现实,想把年度总包谈清楚。
我的看法是,这件事的关键可能不在于具体金额,而在于沟通的时机。谈薪资有时就像市场交易,价格都谈妥准备成交了,突然再来一句“能不能再便宜点”,容易让对方心里产生犹豫。特别是对于45岁这个阶段的候选人,企业招聘时往往更看重稳定和明确的预期,而不是反复的变化。
当然,站在中年人的角度,谨慎一些完全可以理解,毕竟肩上的担子不轻。只是在职场沟通中,清晰和直接有时比“聪明”的试探更重要。这场关于面试谈薪技巧的讨论,也反映出职场沟通的复杂性。
算法题:有趣的电影 – 二级关注者问题
聊完面试,我们切换到一个有趣的算法场景。假设你是一个博主,平台可以是小红书或者B站。
- 直接关注你的人,是你的 一级关注者。
- 这些粉丝自己也会去关注别人,对吧?
- 那些“关注了你的粉丝”的人,但 没直接关注你、也不是你自己,这一部分人,就可以称为 二级关注者。
转换到算法世界,这就是一个典型的有向图问题。一条边 u -> v 表示:u 关注 v。
题目会给你一堆这样的关注关系,再给你一个目标用户 x,要求你计算:所有满足——“关注了某个一级关注者,但自己不是 x,也没直接关注 x”——的用户集合。
输出可以是这个集合的人数,也可以是集合本身,具体看题目要求。
思路拆解:先别急着想DFS、BFS
首先要把方向搞清楚:边是 u -> v(u 关注 v),那么对于用户 v 来说,“谁关注了我”其实是看指向 v 的 入边。
所以,对目标用户 x 的二级关注者计算可以分三步:
- 找一级关注者:找到所有
u,满足有边 u -> x。这就是一级关注者集合 F1。
- 找候选者:对每一个一级关注者
f ∈ F1,找到所有 w,满足 w -> f。这些 w 就是“关注了你粉丝的人”,也就是二级关注者的候选。
- 过滤:把这些候选
w 放入一个集合去重,然后排除掉:
- 用户
x 自己。
- 已经在
F1 中的人(即一级关注者)。
- 不能直接关注
x(上一个条件其实已经保证了这一点)。
本质上,这就是一个 两层“谁关注了谁”的反向查询,严格来说不需要复杂的图算法,核心是理解图的邻居关系。
数据结构怎么选?
这里有两个关键点需要考虑:
- 数据存储是按“谁关注谁”(出边)存,还是“谁被谁关注”(入边)存?
- 查询时,如何尽量减少扫描无关数据?
因为我们的查询需求都是“谁关注了当前这个人”,所以使用 反向邻接表 会非常方便:
followers[v] = 所有满足 u -> v 的 u 的集合
这样做的好处是:
- 建图:一次性扫描所有边
(u, v),将 u 加入 followers[v],时间复杂度 O(m)。
- 查一级关注者:直接就是
followers[x]。
- 查二级候选:对每个一级关注者
f in followers[x],把 followers[f] 合并起来即可。
整体时间复杂度大致是 O(m + |F1| + 所有F1的粉丝总数),在常规场景下完全够用。
下面,我们用一个清晰的函数来实现它。这类图论与数据结构的结合问题在面试中很常见。
- 输入:
n: 用户总数(ID 范围 0 ~ n-1)
edges: 关注关系列表,每个元素是 [u, v] 表示 u 关注 v
target: 目标用户 id
- 输出:
- 目标用户的二级关注者集合(如果只要计数,取
len(result) 即可)
直接上 Python 代码:
from collections import defaultdict
from typing import List, Set
def second_level_followers(n: int, edges: List[List[int]], target: int) -> Set[int]:
"""
计算目标用户的二级关注者集合。
:param n: 用户总数,用户 id 默认是 0 ~ n-1
:param edges: 关注关系列表 [u, v],表示 u 关注 v
:param target: 目标用户 id
:return: 二级关注者的集合
"""
# 构建“被谁关注”的反向邻接表
followers = defaultdict(set) # followers[v] = 关注 v 的所有用户
for u, v in edges:
if 0 <= u < n and 0 <= v < n:
followers[v].add(u)
# 1. 一级关注者:直接关注 target 的人
level1 = followers.get(target, set())
# 2. 所有候选二级关注者:关注了一级关注者的人
candidates = set()
for f in level1:
candidates |= followers.get(f, set())
# 3. 过滤掉不合法的:
# - 不能是自己
# - 不能是一级关注者
# - 也不能是没必要的重复(集合天然去重了)
result = set()
for u in candidates:
if u == target:
continue
if u in level1:
continue
# 如果题目要求“不能直接关注 target”,这里再保险判断一下:
if target in followers and u in followers[target]:
continue
result.add(u)
return result
if __name__ == "__main__":
# 构造示例:
# 0 关注 1
# 2 关注 1
# 3 关注 0
# 4 关注 0
# 5 关注 2
#
# 对于 target = 1:
# 一级关注者:{0, 2}
# 二级关注者候选:
# 关注 0 的有 {3, 4}
# 关注 2 的有 {5}
# 过滤后结果:{3, 4, 5}
n = 6
edges = [
[0, 1],
[2, 1],
[3, 0],
[4, 0],
[5, 2],
]
target = 1
res = second_level_followers(n, edges, target)
print("二级关注者集合:", res)
print("人数:", len(res))
运行上面的代码,你会得到输出:
二级关注者集合: {3, 4, 5}
人数: 3
你看,一道披着“社交”外衣的题目,核心锻炼的是 图建模能力、合理选择数据结构以及基础的集合运算,用 Python 实现起来也非常简洁。技术讨论的魅力就在于此,从一个现实话题可以延伸到严谨的算法世界。这类结合生活场景的算法题,在技术社区如云栈社区中往往能引发更深入的探讨。