Python技术迷

老婆在成都,我在广州,为了结束异地,放弃小鹏高级经理职位,降级去成都要少20万,值得吗?

看到一哥们差点把自己“职业路线”给跑偏了:老婆在成都,他在广州,想结束异地,准备把小鹏高级经理的位置放下,降级去成都,年薪少个20万,问值不值。

Image

程序员看问题喜欢算账:20万是钱,也是压力测试。你在广州赚得多,但两地分居的成本更隐形——机票、高铁、请假、情绪、吵架后的冷战时间,这些都不开发票。

真想去成都,先把条件谈明白:岗位成长、回升通道、住房与家庭分工。钱少点不一定亏,日子顺了,才像把系统从“内存泄漏”修好。

算法题:非递减数列

那个…非递减数列这个题啊,我是有心理阴影的。之前晚上十点准备关电脑打游戏,一个同事在群里来一句:“东哥,数组这题我老是WA,你帮我看一眼?”结果一眼看到了半夜十二点,游戏没打成,Bug还在那儿顽强地跑。

先说人话版啊,非递减数列是啥意思?就是数组 nums 里,从左到右不下降:nums[i] <= nums[i+1],对吧。题目一般还要加个限制:最多改一个元素,问你能不能把整个数组改成非递减的。

听起来像什么?像你有一串同事的绩效分,领导说:你最多帮一个人“润色”一下评分,看能不能做到后面的人不要比前面的人高太多…咳,打个比方。

我当时同事给的那个数组是这样:[4, 2, 3]。肉眼一看就知道,把 4 改成 2 或 1 啥的,就变成 [2,2,3],不就非递减了嘛,so easy。 但问题是,写代码的时候,脑子一紧张,就会开始瞎改,比如有的人直接: 发现 nums[i] > nums[i+1] 就把 nums[i] = nums[i+1],然后就寄了。

我当时跟他说,你别急,咱先想清楚“冲突”出在哪。所谓冲突就是: 只要出现了 nums[i] > nums[i+1],这地方肯定要动手脚,要么改左边,要么改右边,而且这种冲突最多只能出现一次,不然你改一回也救不回来。

思路其实就一条线:

  1. 从左到右扫一遍数组
  2. 每次发现 nums[i] > nums[i+1],记一下“我已经改过一次了”
  3. 如果是第二次发现这种情况,那就直接返回 False
  4. 第一次冲突时,再想一想是该改前面的,还是改后面的

那个关键的第 4 步,很多人容易写糊。你想象一下几种情况:

  • 如果冲突发生在最开头 i == 0,那好说,前面没人,随便你改左还是改右,一般我就偷懒,直接把 nums[i] 变小,等于 nums[i+1]。
  • 如果 nums[i-1] <= nums[i+1],说明把 nums[i] 变成 nums[i+1] 也是安全的,不会破坏前面的顺序。
  • 否则,只能把右边的 nums[i+1] 变大,等于 nums[i],让它别比前面小。

当时我边跟他语音解释,边随手敲了个 Python 小函数,大概长这样:

defcan_be_non_decreasing(nums):
"""
    判断一个数组能不能通过最多修改一个元素,
    变成非递减数列
    """

    changed = False# 标记有没有动过手

for i in range(len(nums) - 1):
if nums[i] <= nums[i + 1]:
continue

# 走到这里说明出现 nums[i] > nums[i+1]
if changed:
# 已经改过一次了,还撞上这种情况,直接GG
returnFalse

        changed = True

# 决定改左边还是改右边
if i == 0or nums[i - 1] <= nums[i + 1]:
# 改左边更安全:让当前值降下去
            nums[i] = nums[i + 1]
else:
# 否则只能把右边抬上来
            nums[i + 1] = nums[i]

returnTrue

这个代码有两个小细节,别看着像废话,其实都是亲身踩坑换来的:

一个是那个 changed 标记,一定要有。你想啊,题目说“最多改一个”,你如果不记一下,循环里发现一次就改一次,那就是“疯狂整容”,直接违规。

另一个是不要一上来就无脑改左边。我当时同事写的是这样:

if nums[i] > nums[i+1]:
if changed:
returnFalse
    changed = True
    nums[i] = nums[i+1]

然后数组 [3, 4, 2, 3] 直接翻车。你可以自己手算下:

  • 下标 1 和 2 冲突:4 > 2,他把 4 改成 2,数组变成 [3, 2, 2, 3]
  • 结果 3 > 2,又冲突一次,挂了

但实际上这题的正确操作是:把 nums[2] 改成 4,变成 [3, 4, 4, 3],然后再看最后一对,还是有问题…哦对,这个例子本来就应该是 False,我嘴快了,你自己再试几个就明白为啥那个逻辑不稳了。

我后来跟他说,写这种“最多改一次”的题,有个通用小套路:

  • 用一个布尔变量记有没有动刀
  • 每次看到不合法的地方,先想想“能不能只通过这一刀解决问题”
  • 如果发现第二个不合法地方,就可以直接返回 False,别犹豫

还有人问,那我能不能不修改原数组?比如有的场景你不想动原始数据,那就简单粗暴一点,先复制一份:

defcan_be_non_decreasing_safe(nums):
    arr = nums[:]  # 浅拷贝一份
return can_be_non_decreasing(arr)

当然实际做题一般不这么矫情,直接改原数组就完事了,面试官也不会在意你那点小改动。

写完给他跑了几个例子:

print(can_be_non_decreasing([4, 2, 3]))        # True
print(can_be_non_decreasing([3, 4, 2, 3]))     # False
print(can_be_non_decreasing([1, 2, 3, 3, 3]))  # True

结果他在群里来一句:“东哥,这不就是扫一遍加个标记吗?好简单哦。” 我当时整个人都无语了,你们知道吧,这种话听着就很想把刚才的代码删了重写一遍,再顺手关电脑睡觉。

反正这个题你记住两点就行: 一个是只允许一处“逆序”,多了必挂; 另一个是碰到“逆序”,先别动手,脑子里先想一下“改左还是改右更安全”,就这点“多想一秒”的功夫,能少很多 Bug。

行了不说了,我去给自己泡个咖啡,你要是把你的写法丢给我,我顺手帮你挑挑毛病,顺便看还能不能优化一行。