Python技术迷

跟一个年薪100万的技术总监吃饭,聊到年底绩效评级,他说决定谁拿A+、谁升职,从来不看这人加了多少班,只看这人的“静音能力”。

刚看到个贴子,说网友跟一个年薪百万的技术总监吃饭,聊到年底绩效。那位总监说,谁拿A+、谁升职,他不看谁加班多,就看一个指标:静音能力,听完确实有点背凉。

Image

网友回帖大概两派:一派骂这是要求员工“闭嘴干活、少提要求”;另一派觉得,情绪稳定、少抱怨,本来就是职场稀缺能力。

我觉得这事吧,关键在于怎么理解“静音”。如果是被压榨也不能吭声,那就是PUA;但如果指的是,遇事不先甩情绪、先把问题搞定,再有理有据地反馈,那就是成熟。

静音能力应该是“情绪自控+高效做事”,而不是“闭嘴忍耐”。一边把事做好,一边少发无效牢骚、敢提有效诉求,这样的静音,才不会把自己静成背景板。

算法题:最大间距

说这个题之前,先给你画个小画面啊:

某天晚上快下班,你 leader 突然甩过来一句——“有个最大间距你写一下,时间复杂度要 O(n) 哦”,然后人就没影了。你一搜发现,排序一把梭就能做,可是 O(n log n) 又不达标,是不是有点烦。

什么是“最大间距”

题意一般是这样的(经典 LeetCode 那个):

给你一个无序的数组 nums,你把它升序排序之后,看看相邻两个数之间的差值,这些差值里最大的那个,就是“最大间距”。

比如:

  • nums = [3, 6, 9, 1]

    • 排序后是 [1, 3, 6, 9]
    • 间距是:3-1=2, 6-3=3, 9-6=3
    • 最大间距 = 3

要求:

  • 时间复杂度 **尽量 O(n)**(理想情况)
  • 不能直接用 sort 当答案(面试官会嫌弃)

当然,如果你在刷题平台上,写个排序版通过一下没问题,先来个最朴素的写法热热身。

先来个“老实人”解法:排序搞定

思路就一句话:

  1. 先排序
  2. 再扫一遍,记录相邻差值最大值

Python 写起来非常顺手:

defmaximum_gap_sort(nums):
if len(nums) < 2:
return0

    nums.sort()
    ans = 0
for i in range(1, len(nums)):
        ans = max(ans, nums[i] - nums[i - 1])
return ans

这个没啥可说的,逻辑很直白,时间复杂度 O(n log n),面试要是没特别卡复杂度,这个都够用。

但题目既然点名了 O(n),那就得上点“技巧活”了。

为啥可以做到 O(n)?——桶的直觉

关键有个抽屉原理的小结论:

  • 假设数组长度是 n,最小值是 min_v,最大值是 max_v
  • 把这 n 个数排好序以后,一共会有 n - 1 个间距
  • 整体跨度是 max_v - min_v
  • 那么至少有一个间距,大于等于:

[ \text{gap_min} = \lceil \frac{\text{max_v} - \text{min_v}}{n - 1} \rceil ]

直白点说:你把 [min, max] 这一整段区间均匀切成 n-1 小段,真实的数据不可能所有间距都比这“平均值”还小,总有一个≥它。

有了这个“理论下限”之后,我们就可以干一件事:

与其真的把所有数排好序,不如把区间切成若干“桶”, 每个桶只记录:这一段里出现过的最小值和最大值。

为啥只存桶内的 min / max 就够?

  • 真正的“最大间距”,不会发生在同一个桶里面(因为桶大小是按上面的最小间距设计的)

  • 只会发生在相邻非空桶之间:

    • 后一个桶的最小值
    • 减去前一个桶的最大值

这样我们就不用真的排序,只是在桶之间跳着看,就能找出最大间距。

把桶法拆开说一下步骤

来,稍微系统一点说(但我尽量不搞得太教科书):

  1. 特判:如果 len(nums) < 2,答案直接是 0,没间距可算。

  2. 扫一遍数组,求出 min_v 和 max_v。

  • 如果 min_v == max_v,说明所有数都一样,最大间距也是 0。
  • 算一个合理的桶大小:

    import math
    bucket_size = math.ceil((max_v - min_v) / (len(nums) - 1))
  • 算桶的个数:

    bucket_count = (max_v - min_v) // bucket_size + 1
  • 为每个桶准备两个数组:

    • bucket_min[i]:第 i 个桶目前看到的最小值
    • bucket_max[i]:第 i 个桶目前看到的最大值 以及一个 bucket_used[i] 记这个桶有没有被用过。
  • 再扫一遍数组,把每个数放进对应桶:

    • 桶编号:idx = (num - min_v) // bucket_size
    • 更新这个桶的 min / max。
  • 最后一次遍历所有桶:

    • 当前桶最小值减前一个桶最大值 = 一个候选间距
    • 不断更新答案
    • 用一个变量 prev_max 记录上一个非空桶的最大值

    • 对每个非空桶:

    注意几个小坑:

    • min_v 和 max_v 自己也会被放进桶,不需要特判扔掉
    • 一定要跳过空桶
    • 桶大小算的时候要用天花板(向上取整),不然可能漏解

    直接上完整代码,你可以对着上面的步骤看:

    import math
    from typing import List

    defmaximum_gap(nums: List[int]) -> int:
        n = len(nums)
    if n < 2:
    return0

        min_v = min(nums)
        max_v = max(nums)
    if min_v == max_v:
    return0

    # 1. 计算桶大小和桶数量
        bucket_size = math.ceil((max_v - min_v) / (n - 1))
        bucket_count = (max_v - min_v) // bucket_size + 1

    # 2. 初始化桶
        bucket_min = [None] * bucket_count
        bucket_max = [None] * bucket_count
        bucket_used = [False] * bucket_count

    # 3. 把每个数丢进桶里
    for num in nums:
            idx = (num - min_v) // bucket_size
    ifnot bucket_used[idx]:
                bucket_min[idx] = num
                bucket_max[idx] = num
                bucket_used[idx] = True
    else:
    if num < bucket_min[idx]:
                    bucket_min[idx] = num
    if num > bucket_max[idx]:
                    bucket_max[idx] = num

    # 4. 扫桶,找最大间距
        prev_max = None
        ans = 0
    for i in range(bucket_count):
    ifnot bucket_used[i]:
    continue
    if prev_max isnotNone:
                ans = max(ans, bucket_min[i] - prev_max)
            prev_max = bucket_max[i]

    return ans

    这个解法的几个点你可以顺便记一下:

    • 时间复杂度:

      • 找 min/max 一遍
      • 分桶一遍
      • 扫桶一遍
      • 总体就是 O(n)
    • 空间复杂度:

      • bucket_count 大致也是 O(n) 级别

    如果真的在面试里,你可以先写排序版的,保证先 AC; 面试官要你优化,再把上面这套桶的思路慢慢说出来,一步步改代码。

    差不多就这样,我去喝口水,你有别的算法题也可以丢过来一起整。

    -END-

    我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

    🔥虎哥私藏精品🔥

    虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB