Python技术迷

去年离职的一位同事,今天突然出现在公司,嘴上说“我又回来上班了”,同事们一个个面无表情,没有回应。

我在网上看到个帖子:去年离职的同事,今天突然杀回公司,进门先来一套“我又回来上班了”,笑得跟过年串门似的。结果全办公室安静得能听见键盘喘气,大家头都没抬,场面那叫一个干。

Image

这事吧,真不稀奇。很多人走的时候都像开了无敌模式,嫌这嫌那,觉得外面遍地机会,老板看了都得主动递工牌。可真出去转一圈才发现,行情这东西,不是你自信它就给你面子。

回来上班不丢人,谁还没看走眼的时候。丢人的是当初把话说太满,像是要去收购全世界,结果四个月后自己先被现实优化了。

职场这地方能安稳发工资,已经算很实在了。

算法题:有序转化数组

有序转化数组这题,别一上来就平方排序

这题刚看题面容易走偏:给你一个有序数组 nums,再给一个二次函数 f(x)=ax^2+bx+c,要求把每个元素变换后,结果仍然按升序返回。

很多人第一反应是这样写:

defsort_transformed_array(nums, a, b, c):
    arr = [a * x * x + b * x + c for x in nums]
    arr.sort()
return arr

能过,大概率也没毛病,但这题真正想考的不是函数怎么套,而是你能不能利用“原数组本身有序”这个条件,把排序省掉。

先看现象。

如果 a > 0,抛物线开口朝上,越靠两边,函数值越大;越靠中间,函数值越小。 如果 a < 0,正好反过来,越靠两边越小,越靠中间越大。

这时候原数组虽然是升序,但变换后的值,不会简单保持原顺序。真正有用的信息是:最大值或者最小值,一定优先出现在两端。

这就很像我们平时做双指针题的味道了。

比如:

nums = [-4, -2, 2, 4]
a, b, c = 1, 3, 5

变换后分别是:

f(-4) = 9
f(-2) = 3
f(2)  = 15
f(4)  = 33

结果是 [3, 9, 15, 33]。

你会发现,两端 -4 和 4 里更容易先产出大值。既然这样,就没必要全量算完再排,直接左右两头比,把大的先塞到结果数组尾巴里就行。

核心代码我一般会这样写:

defsort_transformed_array(nums, a, b, c):
defcalc(x):
return a * x * x + b * x + c

    n = len(nums)
    ans = [0] * n
    left, right = 0, n - 1

# a >= 0 时,大的在两端,从后往前填
# a < 0 时,小的在两端,从前往后填
    idx = n - 1if a >= 0else0

while left <= right:
        lv = calc(nums[left])
        rv = calc(nums[right])

if a >= 0:
if lv > rv:
                ans[idx] = lv
                left += 1
else:
                ans[idx] = rv
                right -= 1
            idx -= 1
else:
if lv < rv:
                ans[idx] = lv
                left += 1
else:
                ans[idx] = rv
                right -= 1
            idx += 1

return ans

这段代码不用排序,时间复杂度是 O(n),空间复杂度 O(n)。

这题真正容易卡住的地方,不是双指针本身,而是很多人会在脑子里默认“有序数组经过函数变换,大概还是有序的”。这在一次函数里还有点机会,到了二次函数基本就不成立了。

你可以自己打个小样本感受一下:

defcalc(x, a, b, c):
return a * x * x + b * x + c

nums = [-3, -1, 0, 2]
print([calc(x, 1, -2, 1) for x in nums])

输出可能是:

[16, 4, 1, 1]

原数组是升序,结果已经完全不是升序了。

再往下拆,其实这题本质是“抛物线 + 双端选择”。 你不用真的去算顶点坐标,也不用分类讨论太复杂,只需要抓住一件事:两端一定更值得先比较。

面试里要是时间紧,我会先给出朴素解法,再补一句优化思路:

defsort_transformed_array_v1(nums, a, b, c):
return sorted(a * x * x + b * x + c for x in nums)

然后再说,既然 nums 已经有序,二次函数在数轴上的值分布有明显结构,可以用双指针把 O(n log n) 压到 O(n)。这样答,思路会更完整一点,不会显得只会背答案。