Python技术迷

某节员工爆料:我们运营负责人,44岁,手里管:着几十个项目。他定了个死规矩:每周四晚上8点必须离开

刚刷到这个帖子,第一反应是:这负责人有点反职场常识啊。

字节一个运营负责人,44岁,手里压着几十个项目,按理说这种人最容易卷到飞起,天天把“今晚辛苦一下”挂嘴边。结果他给团队定了个规矩:每周四晚上8点,必须走人,谁也别在公司装忙。

Image

这事听着简单,其实挺狠的。因为很多加班根本不是活干不完,是没人敢先走。领导不走,下面人就开始演:电脑开着,文档点着,脑子已经下班了。

他这个规矩等于直接把戏台子拆了。到点走,别磨叽,别假装敬业。你真有急活,提前排;你总是拖到晚上,那就是管理问题。

HR看完估计都沉默,打工人看完倒是挺想转发给自己老板。周四8点下班,听着像福利,其实是领导终于像个人了。

今日算法题

数组里删元素,最容易写出一个看着没毛病、跑几个用例也没毛病、最后一提交就炸的代码。

比如这个题:给一个数组 nums,再给一个值 val,要求把数组里等于 val 的元素移掉,并返回剩下元素的个数。

注意,题目要的是原地修改。

这四个字我一般会多看一眼。因为很多人第一反应是新建一个数组:

left = []
for x in nums:
if x != val:
        left.append(x)

这个写法当然舒服,但不是题目要的。面试里这么写,基本等于告诉对方:我没处理原地数组。

还有一种更坑的写法,是一边遍历一边 remove:

for x in nums:
if x == val:
        nums.remove(x)

这代码我第一眼就不太信。

因为 remove 会移动后面的元素,遍历指针还在往后走,很容易漏删。比如:

nums = [3, 3, 2, 3]
val = 3

删掉第一个 3 后,数组变成:

[3, 2, 3]

原来第二个 3 被挪到前面了,但循环已经往后走了,它就被跳过去了。

这题比较稳的写法,是用两个位置。

一个位置负责扫描,另一个位置负责记录“下一个有效元素应该放哪里”。

代码我一般这么写:

classSolution:
defremoveElement(self, nums: list[int], val: int) -> int:
        write = 0

for read, num in enumerate(nums):
if num == val:
continue

            nums[write] = num
            write += 1

return write

这里的 read 只是往前扫,看到什么处理什么。

write 不一样,它只在遇到有效元素的时候才往前走。

拿这个数组跑一下:

nums = [0, 1, 2, 2, 3, 0, 4, 2]
val = 2

扫描过程大概是这样:

read=0, num=0,留下,写到 nums[0]
read=1, num=1,留下,写到 nums[1]
read=2, num=2,跳过
read=3, num=2,跳过
read=4, num=3,留下,写到 nums[2]
read=5, num=0,留下,写到 nums[3]
read=6, num=4,留下,写到 nums[4]
read=7, num=2,跳过

最后数组前半部分变成:

[0, 1, 3, 0, 4]

返回值是:

5

后面的内容不用管,题目只检查前 5 个元素。

这个地方也容易犯一个小毛病:非要把数组后面的元素删干净。

没必要。

LeetCode 这类原地题,经常只认返回长度 k,然后检查 nums[0:k]。你把后面改成什么,都不影响结果。线上写业务代码当然不能这么随便,但算法题就按题意来,不要给自己加活。

如果题目说元素顺序可以改变,还能写另一种版本:从尾部拿元素覆盖前面的 val。这个写法在要删除的元素很多时,赋值次数可能更少。

classSolution:
defremoveElement(self, nums: list[int], val: int) -> int:
        i = 0
        end = len(nums)

while i < end:
if nums[i] == val:
                nums[i] = nums[end - 1]
                end -= 1
else:
                i += 1

return end

这段代码有个细节:遇到 val 后,i 不能马上加一。

因为从尾部换过来的新元素还没检查。它可能还是 val。这个坑挺常见,尤其是数组尾部连续几个都是目标值的时候。

比如:

nums = [2, 3, 2]
val = 2

第一次把最后一个 2 换到前面,如果 i 直接加一,前面的 2 就漏了。

所以这题要记住的不是“双指针”这个词,而是两个判断:

扫描位置是不是会被元素移动影响?

写入位置是不是只为有效元素服务?

这两个想清楚,代码就短了,也不容易乱。