某节员工爆料:我们运营负责人,44岁,手里管:着几十个项目。他定了个死规矩:每周四晚上8点必须离开
刚刷到这个帖子,第一反应是:这负责人有点反职场常识啊。
字节一个运营负责人,44岁,手里压着几十个项目,按理说这种人最容易卷到飞起,天天把“今晚辛苦一下”挂嘴边。结果他给团队定了个规矩:每周四晚上8点,必须走人,谁也别在公司装忙。
这事听着简单,其实挺狠的。因为很多加班根本不是活干不完,是没人敢先走。领导不走,下面人就开始演:电脑开着,文档点着,脑子已经下班了。
他这个规矩等于直接把戏台子拆了。到点走,别磨叽,别假装敬业。你真有急活,提前排;你总是拖到晚上,那就是管理问题。
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 就漏了。
所以这题要记住的不是“双指针”这个词,而是两个判断:
扫描位置是不是会被元素移动影响?
写入位置是不是只为有效元素服务?
这两个想清楚,代码就短了,也不容易乱。