Python技术迷

公司裁员名单下来了,那个36岁的工程师稳如泰山,反而几个28岁的年轻人走了。

刚看到个贴子,说公司裁员名单下来了,居然是几个28岁的年轻人走了,那个36岁的工程师稳得一批。

Image

我觉得这事吧,其实一点都不意外。但我觉得职场不是看年龄,是看价值。

从我的角度看,36岁的工程师能稳住,多半是因为技术扎实、可替代性低,像螺丝钉里那颗拔了就会漏油的。

他不一定最能加班,但一定解决过别人解决不了的问题。而那些年轻人,可能冲劲有了,可经验还没积出来,老板一算账,换谁都能干,那最后只能牺牲“最便宜”的位置。

不过话说回来,这事也提醒年轻人:别光想着卷工时,最关键还是提升不可替代性。你能解决问题,公司才舍不得动你。

面试题:数组列表中的最大距离

有一个数组(Python 里就是 list),你要在里面找一对位置 i 和 j,满足:

  • j >= i
  • nums[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 风扇都要起飞。

想快一点,就得少「乱看」

那怎么优化呢?直觉是这样的: 我们之所以会写双重循环,是因为「不知道」哪些组合肯定不行,只好都看。 那如果我们能提前算出:

  • 对于某个位置左边,最小的值是多少
  • 对于某个位置右边,最大的值是多少

是不是就更有信息了?

我们来做两件小事:

  1. 从左到右,算一个 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