公司裁员名单下来了,那个36岁的工程师稳如泰山,反而几个28岁的年轻人走了。
刚看到个贴子,说公司裁员名单下来了,居然是几个28岁的年轻人走了,那个36岁的工程师稳得一批。
我觉得这事吧,其实一点都不意外。但我觉得职场不是看年龄,是看价值。
从我的角度看,36岁的工程师能稳住,多半是因为技术扎实、可替代性低,像螺丝钉里那颗拔了就会漏油的。
他不一定最能加班,但一定解决过别人解决不了的问题。而那些年轻人,可能冲劲有了,可经验还没积出来,老板一算账,换谁都能干,那最后只能牺牲“最便宜”的位置。
不过话说回来,这事也提醒年轻人:别光想着卷工时,最关键还是提升不可替代性。你能解决问题,公司才舍不得动你。
面试题:数组列表中的最大距离
有一个数组(Python 里就是 list),你要在里面找一对位置 i 和 j,满足:
j >= inums[j] >= nums[i](后面的值不比前面小)并且这个「距离」 j - i要尽可能大
最后返回这个最大的距离是多少。
比如:[4, 3, 5, 2, 1, 6]这里最划算的一对是:前面的 4(下标 0)和最后的 6(下标 5),因为 6 >= 4,而且距离 5 - 0 = 5,你翻一翻会发现没有比 5 更大的合法距离了。
先来一个最直观但有点笨的写法
很多人第一反应都是:那我就两重循环呗,外层枚举 i,内层枚举 j,只要条件满足就更新一下最大值。
说白了就是:
把所有可能的 (i, j)对都看一遍找到满足 nums[j] >= nums[i]的顺便把 j - i的最大值记下来
用 Python 写出来差不多这样:
defmax_distance_bruteforce(nums):
n = len(nums)
if n == 0:
return0
ans = 0
for i in range(n):
for j in range(i, n):
if nums[j] >= nums[i]:
ans = max(ans, j - i)
return ans
这个写法逻辑很直白,但有个致命问题:时间复杂度是 O(n^2)。 数组一长,比如十万级,直接原地爆炸,CPU 风扇都要起飞。
想快一点,就得少「乱看」
那怎么优化呢?直觉是这样的: 我们之所以会写双重循环,是因为「不知道」哪些组合肯定不行,只好都看。 那如果我们能提前算出:
对于某个位置左边,最小的值是多少 对于某个位置右边,最大的值是多少
是不是就更有信息了?
我们来做两件小事:
从左到右,算一个
left_min数组
left_min[i]表示从 0 到 i 这段里的最小值
从右到左,算一个 right_max 数组
right_max[j]表示从 j 到 n-1 这段里的最大值
举个例子:
nums = [4, 3, 5, 2, 1, 6]
算 left_min:
位置 0:最小就是 4 → 4位置 1:min(4,3) → 3位置 2:min(3,5) → 3位置 3:min(3,2) → 2位置 4:min(2,1) → 1位置 5:min(1,6) → 1
所以 left_min = [4, 3, 3, 2, 1, 1]
再算 right_max(从右往左):
位置 5: 6位置 4:max(1,6) → 6位置 3:max(2,6) → 6位置 2:max(5,6) → 6位置 1:max(3,6) → 6位置 0:max(4,6) → 6
所以 right_max = [6, 6, 6, 6, 6, 6]
那有了这两个数组之后,我们怎么用呢?
双指针的核心小心思
我们现在不再直接看原数组,而是看 left_min 和 right_max。
用两个指针:
i指向左侧(对left_min)j指向右侧(对right_max)
初始都从 0 开始。然后有一个很关键的逻辑:
如果 left_min[i] <= right_max[j]说明:存在一个左边某个位置(<= i)上的值,能和右边某个位置(>= j)上的值凑出合法的一对(后者不小于前者)。 既然条件满足了,那我们可以放心地更新一下ans = max(ans, j - i),然后尝试把j往右挪,看能不能距离再拉大一点。如果 left_min[i] > right_max[j]说明即便我们在左边用到了「最小」那一个值,它都比右边最大的还大,那这个i再怎么和这个j后面的组合都没希望了,只能把i往右挪一个,换一个更靠右的左端点试试。
就这么左右指针一边走一边比,一次循环就搞定了,时间复杂度是 O(n)。
完整 Python 实现(高效版)
from typing import List
defmax_distance(nums: List[int]) -> int:
n = len(nums)
if n == 0:
return0
# 1. 预处理 left_min:左侧最小值
left_min = [0] * n
left_min[0] = nums[0]
for i in range(1, n):
left_min[i] = min(left_min[i - 1], nums[i])
# 2. 预处理 right_max:右侧最大值
right_max = [0] * n
right_max[-1] = nums[-1]
for i in range(n - 2, -1, -1):
right_max[i] = max(right_max[i + 1], nums[i])
# 3. 双指针扫描
i = j = 0
ans = 0
while i < n and j < n:
if left_min[i] <= right_max[j]:
# 条件满足,更新最大距离,然后扩大右边范围
ans = max(ans, j - i)
j += 1
else:
# 条件不满足,只能右移左指针,让左边变“更小”一点
i += 1
return ans
if __name__ == "__main__":
nums = [4, 3, 5, 2, 1, 6]
print(max_distance(nums)) # 输出 5
这个版本的几个点简单总结一下(别当成公式背,理解就好):
预处理两个辅助数组,都是一遍循环搞定, O(n)。双指针部分也是一遍扫过去,最多每个指针走 n步,所以还是O(n)。空间多花了两个长度为 n的数组,所以空间复杂度是O(n)。
如果你以后遇到变种,比如要的是 nums[j] > nums[i](必须严格大于),那就把比较条件里的 <= 换成 < 就行,整体思路不变。
差不多就这样,如果你愿意我还能帮你改成「返回是哪一对下标」,或者顺便写个单元测试版本。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB