公司烂到候选人还没面试,先在网上把雷踩完了,这招聘还怎么做。

最惨的还不是公司名声臭,是臭得太稳定。你解释半天,人家回去一搜,清一色“避雷”“别来”“入职即后悔”,HR那点话术当场碎一地。约好的面试,临到头放鸽子,简直跟下班打卡一样准时。
评论区也挺损。有人说,这种公司属于自带反向背调,连试用期都省了。还有人说,能把口碑做成行业通缉令,也算企业文化特别鲜明。
公司前面拼命作,后面让HR拿着破网去捞鱼,捞不上来还得写复盘,想想都头大。做招聘做到这份上,跟拿着漏勺舀水有什么区别?
不过,吐槽归吐槽,活儿还得干。平日里多刷刷 面试求职 相关的内容,看看同行是怎么在这种逆风局里捞人的,偶尔也能找到点新思路——比如怎么在薪资谈判里多争取一点空间,或者怎么重新包装那些确实还过得去的岗位亮点。虽然大环境改不了,但至少心里有个谱。
算法题:最大矩形
柱状图一摆出来,很多人第一反应就是暴力:以每一列为高,往左右扩。代码也不是不能写,就是一提测,数据量一上来,直接老实了。
这题叫“最大矩形”,看着像几根柱子拼面积,真下手的时候,坑基本都在“你以为当前柱子能撑多宽”。这地方我一般不先算面积,先找边界:当前柱子左边第一个比它矮的是谁,右边第一个比它矮的是谁。边界一出来,宽度就出来了,面积自然也就出来了。
最顺手的做法就是 单调栈。
栈里放下标,不放高度。原因很简单,后面算宽度要用位置,光有高度没法减。栈里保持“对应高度递增”,一旦遇到一个更矮的柱子,说明前面那些更高的柱子,右边界终于等到了,该结算了。
先看代码,代码我自己按平时写法收了收,不整那些花里胡哨的类封装:
def largest_rectangle_area(heights: list[int]) -> int:
# 头尾补 0,目的是把栈里剩下的柱子一次性清干净
nums = [0] + heights + [0]
stack = []
ans = 0
for i, h in enumerate(nums):
while stack and nums[stack[-1]] > h:
mid = stack.pop()
height = nums[mid]
left = stack[-1] # 左边第一个更矮的位置
width = i - left - 1
area = height * width
if area > ans:
ans = area
stack.append(i)
return ans
这段代码短,但别小看,里面其实把最麻烦的边界处理顺手给做掉了。
比如输入:
heights = [2, 1, 5, 6, 2, 3]
print(largest_rectangle_area(heights))
结果是:
10
这个 10 怎么来的?不是最高的 6 单独算,也不是把所有柱子硬拼。真正最大的,是高度 5 和 6 这两根组成的宽度 2 的矩形,面积 5 * 2 = 10。
这里有个细节,很多人第一次写会卡住:为什么弹栈时,i 就能当右边界?
因为当前柱子 h 比栈顶对应的柱子矮。那就说明,栈顶那根柱子最多只能延伸到 i - 1,到 i 这儿已经撑不住了。左边界则是弹栈之后新的栈顶,因为它正好是“左边第一个更矮的柱子”。
说白一点,某根柱子真正结算面积的时机,不是它入栈的时候,而是它第一次遇到比自己矮的柱子的时候。
再拿一组特别容易写错的例子看下:
heights = [2, 2, 2]
print(largest_rectangle_area(heights))
答案是 6。
这题相等高度怎么处理,经常有人纠结。我的习惯是栈里保持严格递增还是非严格递增都能做,但代码得统一。上面这版用的是:
while stack and nums[stack[-1]] > h:
也就是只有“前一个严格大于当前值”才弹栈。这样相同高度会先一起压着,最后统一结算,不容易把宽度切碎。
这题本质上不是数学题,是边界题。你要是上来就盯着“每根柱子能组成多大面积”,大概率会在左右扩展那里绕半天。先把问题翻译成“找每个柱子左边和右边第一个更小值的位置”,脑子就清楚了。
时间复杂度是 O(n),因为每个下标最多入栈一次、出栈一次。这个复杂度才是这题该有的样子。再往回写双层循环,那就不是做题了,是拿 CPU 出气。
这种题我一般会多看两眼补 0 的写法。别嫌这招土,线上代码里这种“前后塞哨兵,把分支打平”的手法很值钱,判断少,边界稳,不容易漏。
这题做完,记住一件事就够了:单调栈不是为了装下标,是为了延迟结算面积。 什么时候该算?遇到更矮的那一刻。到这题基本就通了。