最近网上有个帖子讨论挺热烈:一位面试官遇到了要价28K的运维候选人,简历上赫然写着“精通Linux/Shell”。现场出了一道题,要求写命令统计Nginx日志里访问量Top 10的IP。结果候选人憋了半天,只敲出了一个cat,后续的awk、sort、uniq等常用命令一个都不会。

网友的回复也挺两极分化:有人直呼这是“PPT工程师”,也有人觉得现场紧张难免,面试官不必如此刁难。
要我说,问题的关键不在于能否瞬间写出那条命令,而在于你的薪资期望和实际能力是否匹配。敢开出28K的价码,就得确保简历上“精通”二字的分量,不能仅仅是会cd和ls,基础的文本处理三剑客(awk, sort, uniq)得真的用顺手才行。对于运维或开发岗位,扎实的Shell脚本能力是排查问题、分析日志的日常基本功。
反过来讲,面试官出题也别搞成纯“知识竞赛”或脑筋急转弯。与其死记命令,不如多聊聊实际案例,比如“服务器突然响应变慢,你怎么一步步排查?”这类问题,更能看出一个人的实战经验和解决问题的思路。说到底,平时踏实提升运维硬实力,写简历时诚实不注水,比任何华丽的包装都来得实在。
算法题:整数转罗马数字
聊完面试,再说个算法题放松下。昨晚十一点多,我在公司楼下啃着冷掉的外卖,组里的小李跑过来问:“东哥,LeetCode上那个‘整数转罗马数字’你咋写的?我写的总对一半错一半。”
我当时困得迷糊,但这题印象挺深,就给他顺嘴讲了一遍。
题目很简单:给你一个1到3999之间的整数,把它转换成罗马数字。规则是几个基本符号:I(1), V(5), X(10), L(50), C(100), D(500), M(1000)。组合规则有“加法”和“减法”两种:
- 加法:小数在大数右边,表示相加,如 III = 1+1+1 = 3。
- 减法:小数在大数左边,表示大数减小数,如 IV = 5-1 = 4, IX = 10-1 = 9。
同理,40是XL(50-10),90是XC,400是CD,900是CM。记住“减法都是小的放左边”就行。
我跟小李说,你别管历史故事,咱就把它当成一个“找零钱”问题。假设你有一堆特殊面额的纸币:[1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],对应的“币面”图案是:[“M”, “CM”, “D”, “CD”, “C”, “XC”, “L”, “XL”, “X”, “IX”, “V”, “IV”, “I”]。
规则超简单:从最大面额开始,看看当前数字能用几张这个面额的,能用就用,数字减去相应的值,然后继续用这个面额,直到用不了再换下一个更小的面额。这就是“贪心算法”。
以1994为例:
- 最大面额1000(M),能用1张,数字剩994,结果字符串加“M”。
- 下一档900(CM),能用1张,数字剩94,结果加“CM”。
- 500(D)和400(CD)都用不了,跳过。
- 100(C)用不了,跳过。
- 90(XC)能用1张,数字剩4,结果加“XC”。
- 最后4正好用一张IV,结果加“IV”。
- 拼起来就是 “M” + “CM” + “XC” + “IV” = “MCMXCIV”。
我当时在他电脑上直接敲了个Python版,逻辑很清晰:
def int_to_roman(num: int) -> str:
"""
把 1~3999 的整数转成罗马数字
"""
if not 1 <= num <= 3999:
raise ValueError("只支持 1 ~ 3999 之间的整数")
# 从大到小枚举所有合法的“面额”和它对应的罗马符号
mapping = [
(1000, "M"),
(900, "CM"),
(500, "D"),
(400, "CD"),
(100, "C"),
(90, "XC"),
(50, "L"),
(40, "XL"),
(10, "X"),
(9, "IX"),
(5, "V"),
(4, "IV"),
(1, "I"),
]
res = []
for value, symbol in mapping:
# 这个面额最多能用多少次
count = num // value
if count: # 大于 0 再处理,少做点无用功
res.append(symbol * count)
num -= value * count
if num == 0:
break
return "".join(res)
if __name__ == "__main__":
tests = [3, 4, 9, 58, 1994]
for t in tests:
print(t, "=>", int_to_roman(t))
小李当时问了个好问题:“东哥,这里为啥能贪心啊?会不会先不用大面额,后面组合起来更优?”
我说,你仔细想,我们mapping列表里那13个组合,本身就是罗马数字官方规定的、不可再分的最优“积木块”。比如9,官方写法就是“IX”,你不能用“VIIII”。既然最小的合法单位都固定好了,而且我们从大到小拿“积木”,就永远不会出现“拿了大块导致后面没法拼”的情况。所以这个贪心是严格正确的,不是拍脑袋的。
写的时候还有几个细节要注意:
- 范围校验:题目通常限定1~3999,面试时可能不强制校验,但如果写成工具函数,最好加上
ValueError,边界清晰,调用方错了也容易定位。
- 实现变体:也有人喜欢用
while循环一次次减,像下面这样:
def int_to_roman_slow(num: int) -> str:
mapping = [
(1000, "M"),
(900, "CM"),
# ...
(1, "I"),
]
res = ""
i = 0
while num > 0:
value, symbol = mapping[i]
if num >= value:
res += symbol
num -= value
else:
i += 1
return res
这种写法也对,逻辑稍微绕一点,在3999这个极小范围内性能没区别。面试时能清晰写出任何一种都可以,关键是别写错逻辑。
无论是准备算法/数据结构面试,还是应对运维开发中的实际问题,核心都是对基础知识的深刻理解和灵活运用。大家怎么看这个面试案例和算法题?欢迎来云栈社区分享你的见解和经验。