Python技术迷

5B的同学吐槽说,他们HR每天像守株待兔一样盯着监控。有人刚离开工位,系统还没反应,HR已经开始记录离岗次数了。

刚看到个贴子,说的是5B那边的同学吐槽公司管理方式,核心就一个字:盯。HR天天守着监控,人刚起身倒杯水,记录已经记上了。

Image

我觉得这事吧,问题不在技术,在心态。贴子里描述的感觉,已经不像正常办公,更像进了流水线工厂,人在工位上不是干活,是随时等着被抓现行。

网友回帖里不少人也炸了,有人说这是“反向激励”,还有人直接劝快跑,说这种地方仲裁一告一个准。我比较认同后一种,但也得说句实话,公司敢这么搞,说明内部早就不信任员工了。

怎么说呢,管理就像拴风筝,线太松会飞走,线太紧直接断。一天到晚算离岗次数、扣钱罚款,短期可能“纪律好了”,长期只会把人心耗光。大家来上班是交换价值,不是来参加真人版监控秀。

真正靠谱的管理,是让人把事干好,而不是把人盯死。公司想走远,先把格局放大点吧。

面试题:连续空余座位

想象下,周五下班,你在手机上打开选座页面,一排小圆点,有灰有蓝,你跟朋友说“我要三连坐,中间别夹人”,其实这道“连续空余座位”的算法题,说的就是这个事。

一般会有这么几种常见说法,意思差不多:

  • 已知一排座位,用 0 表示空,1 表示有人,比如 [1,0,0,1,0,0,0]
  • 问你:能不能给出 k 个连续的空位?要的话,把起始位置返回
  • 或者再顺带问一句:这一排里,最长连续空位有多长,从哪开始

所以核心就是:在一个 0/1 数组里,找最长的 0 段,或者找有没有长度 ≥ k 的 0 段。

先写个最实用的:能不能安排 k 个连续座

就按电影院选座来想,你从左到右扫一遍,看到空位就计数,遇到有人就清零,谁先数到 k,谁赢,对吧。

用 Python 写出来,很顺手:

from typing import List, Optional, Tuple

deffind_continuous_seats(seats: List[int], k: int) -> Optional[Tuple[int, int]]:
"""
    在一排座位中找到第一个可以坐下 k 个人的连续空位区间
    :param seats: 0 表示空位,1 表示已占
    :param k: 需要连续空位的个数
    :return: (start, end) 下标区间,找不到就返回 None
    """

    count = 0# 当前已经连续的空位数量
    start = 0# 当前这一段连续空位的起始下标

for i, seat in enumerate(seats):
if seat == 0:
# 新的一段空位开始
if count == 0:
                start = i
            count += 1

# 一旦长度够了,直接返回这一段
if count >= k:
return start, start + k - 1
else:
# 遇到有人,连续被打断,计数清零
            count = 0

# 扫完还没找到
returnNone

if __name__ == "__main__":
    seats = [1, 0, 0, 1, 0, 0, 0]
    k = 3
    res = find_continuous_seats(seats, k)
    print(res)  # 输出:(4, 6),表示 4 到 6 位置可以坐 3 个人

这个思路就一个关键词:一次遍历。

  • 从左往右扫一遍,时间复杂度 O(n)
  • 只用了几个变量,空间复杂度 O(1)
  • 不需要什么花里胡哨的数据结构,面试官问也好讲

再顺带把“最长连续空位”也算出来

现实里你可能还想知道:这一排里最舒服的一段空位有多大,方便给晚来的大部队预留下。

这个跟上面差不多,只不过不是发现 ≥k 就返回,而是全程记录一个“历史最优”:

deflongest_empty_block(seats: List[int]) -> Tuple[int, int, int]:
"""
    找到最长的一段连续空位
    :param seats: 0 表示空位,1 表示已占
    :return: (max_len, start, end)
    """

    max_len = 0
    max_start = -1
    max_end = -1

    cur_len = 0
    cur_start = 0

for i, seat in enumerate(seats):
if seat == 0:
if cur_len == 0:
                cur_start = i
            cur_len += 1

if cur_len > max_len:
                max_len = cur_len
                max_start = cur_start
                max_end = i
else:
# 这一段结束了
            cur_len = 0

return max_len, max_start, max_end

if __name__ == "__main__":
    seats = [1, 0, 0, 1, 0, 0, 0]
    print(longest_empty_block(seats))  # (3, 4, 6)

逻辑上就是:

  • cur_len 记录“当前这段空位连续了多长”
  • 一旦遇到 1,就说明这段断了,cur_len 清零
  • 每次 cur_len 变大,就拿它和 max_len 比,比赢了就更新“最佳答案”

所以这个函数不但告诉你最长空位的长度(比如 3 个座),还告诉你是从哪到哪。

如果再实际一点,加个边界校验

日常写业务代码,肯定要多啰嗦两句,防一手奇怪的入参,不然排查起来特别烦:

defsafe_find_continuous_seats(seats: List[int], k: int) -> Optional[Tuple[int, int]]:
if k <= 0:
raise ValueError("k 必须是正整数")

ifnot seats:
returnNone

# 简单防一手非法数字
for x in seats:
if x notin (0, 1):
raise ValueError("座位数组只能包含 0 或 1")

return find_continuous_seats(seats, k)

这种小题,很多时候不是算法本身有多难,更多是看你有没有把“正常情况 + 极端情况”都想明白:

  • 全是 1 怎么办?——函数老老实实返回 None
  • 全是 0 怎么办?——最长那段就是整排
  • k 比座位数还大?——照样返回 None
  • k 是 0 或负数?——直接抛异常,别装作正常

顺便说一句滑动窗口

有的小伙伴一看到“连续”“区间”这种词,就会想到滑动窗口,其实上面的写法,本质就是最简单那种“指针往前走 + 计数”的滑动窗口,只是我们没特意给两个指针起名字而已。

如果真的想写成两个指针的味道,也可以这样:

deffind_continuous_seats_window(seats: List[int], k: int) -> Optional[Tuple[int, int]]:
    left = 0
    zero_count = 0# 当前窗口里的空位数

for right, seat in enumerate(seats):
if seat == 0:
            zero_count += 1
else:
# 窗口里混进去了 1,就把 left 挪到 right+1
            left = right + 1
            zero_count = 0

if zero_count >= k:
return right - k + 1, right

returnNone

味道差不多,只是你脑子里更容易把它和“滑动窗口这个套路”对应起来,后面刷别的题也能用同一套思路。

差不多就这样,这个题一般就是这么一遍扫过去的事儿,多写两遍手就熟了,面试碰到也不紧张。

-END-

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

🔥虎哥私藏精品🔥

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