Python技术迷

我和对象都是外包,现在我转正了,要不要分手

这帖一看就挺职场。外包的时候同病相怜,晚上吐槽甲方、吐槽流程、吐槽工牌颜色都能聊半天。结果你一转正,心态马上变了,开始担心对象会不会拖自己后腿,圈子、收入、稳定性是不是慢慢拉开。

感情这玩意,难说呀

Image

评论区也挺真实。有人说“能问出来,其实心里已经偏了”。还有人更损,说“今天嫌外包,明天是不是嫌非大厂”。这话难听,但不是没道理。

我个人看,这事重点根本不在外包不外包,在你转正以后第一反应是庆祝,还是赶紧给对象做风险排查。你要是真嫌弃,就别拿职业身份当挡箭牌。成年人谈恋爱,聊前途可以,别聊着聊着把人聊成简历筛选。

算法题:戳气球

一上来就贪心,基本都会死在这题上。

看到“戳破一个气球,得分 = 左边气球 * 当前气球 * 右边气球”,很多人第一反应是:那我每次先戳最大的,或者先戳乘积最大的。写两组数据一跑,分数就开始拧巴。这题别顺着“先戳谁”想,越想越乱。真正难的地方不在怎么戳第一个,而在最后一个戳的是谁。

这类题我一般先怀疑区间 DP。因为一旦题目里出现“删掉一个元素后,左右关系变了”,你正着模拟往往很难维护现场,反过来想最后一步,状态就干净了。

比如 nums = [3,1,5,8],题目默认两边补 1,实际处理时我会先变成:

nums = [1, 3, 1, 5, 8, 1]

然后定义 dp[i][j] 表示:只戳开区间 (i, j) 里的气球,能拿到的最高分。注意这里是开区间,不包括 i 和 j。这地方如果定义成闭区间,转移很容易把自己绕进去。

为什么这样定义?因为假设 k 是区间 (i, j) 里最后一个被戳掉的气球,那它最后拿到的分数就是:

nums[i] * nums[k] * nums[j]

这就很舒服了。因为在 k 之前,(i, k) 和 (k, j) 这两段已经各自戳完,互不干扰。所以转移方程直接出来:

dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + nums[i] * nums[k] * nums[j])

完整代码我会写成这样,够短,也够现场:

defmaxCoins(nums: list[int]) -> int:
    arr = [1] + nums + [1]
    n = len(arr)
    dp = [[0] * n for _ in range(n)]

# 区间长度至少得有 3,开区间里才有气球可戳
for length in range(3, n + 1):
for left in range(0, n - length + 1):
            right = left + length - 1

for k in range(left + 1, right):
                score = dp[left][k] + dp[k][right] + arr[left] * arr[k] * arr[right]
if score > dp[left][right]:
                    dp[left][right] = score

return dp[0][n - 1]

这段代码没花活,三层循环,时间复杂度 O(n^3),空间复杂度 O(n^2)。看到三层循环别先慌,这题本来就不是线性能做掉的类型。面试里你要是还在那儿抠什么“能不能双指针优化”,大概率方向已经偏了。

拿 nums = [3,1,5,8] 跑一下,结果是 167。

还有两个地方特别容易写错。

第一个,边界一定要补两个 1。不补,最左和最右的乘积没法统一处理,转移代码会开始长分支,一长就容易错。

第二个,遍历顺序必须按区间长度从小到大来。因为 dp[i][j] 依赖更短的子区间,顺序不对,取到的就是半成品。

有同学会问,为什么不能记忆化搜索?能,当然能,核心还是这一套转移。只是我自己写这题,更偏向直接上递推表,排查起来更直观,尤其是你想现场打印某个区间的值时,一眼就能看见。

再给个记忆化版本,逻辑其实一样:

from functools import lru_cache

defmaxCoins(nums: list[int]) -> int:
    arr = [1] + nums + [1]

    @lru_cache(None)
defdfs(left: int, right: int) -> int:
if left + 1 >= right:
return0

        best = 0
for k in range(left + 1, right):
            best = max(
                best,
                dfs(left, k) + dfs(k, right) + arr[left] * arr[k] * arr[right]
            )
return best

return dfs(0, len(arr) - 1)

这题说到底,不是“戳气球”,是典型的把过程题改写成最后一步决策题。这个弯一旦拐过来,题目就老实了。拐不过来,就会一直在“先戳哪个”里打转。这个味道,跟线上排查很像:现象越乱,越别跟着乱跑,先找一个稳定的观察点。