程序员老鬼

虚报了薪资流水入职后,公司要把我开除!每月薪资10000不到 我说我每个月25000

我在网上看到一个帖子,有人吐槽:面试时把工资吹高了,实际月薪一万不到,硬说自己有2万5,想冲3万。结果公司真给了offer,报道时又要求交一年薪资流水,这下好了,牛皮吹出去,回旋镖嗖一下就飞回来了。

Image

面试想抬价,能理解,谁不想多挣点钱啊,打工人看到工资数字,眼睛都能亮两度。但你一下从一万吹到两万五,这就不是包装了,这是给自己开了美颜十级滤镜。公司这边也不算厚道,真有疑问早点说,别等人入职了再翻桌子。

说到底,找工作这事还是别玩太大,薪资可以谈,话不能飘,不然最后班没上稳,仲裁还得自己跑,纯纯给生活加了个副本。

面试题:有序转化数组

这题我第一次看到的时候,直觉很容易写成两步:

先把数组里每个数都代进公式 f(x) = ax² + bx + c,再整体排个序。

代码不复杂,但这题既然给了原数组有序这个条件,基本就在提醒你:别直接把它浪费掉。

先看一个最直接、也最容易想到的写法:

publicint[] sortTransformedArray(int[] nums, int a, int b, int c) {
int[] ans = newint[nums.length];
for (int i = 0; i < nums.length; i++) {
        ans[i] = calc(nums[i], a, b, c);
    }
    Arrays.sort(ans);
return ans;
}

privateintcalc(int x, int a, int b, int c){
return a * x * x + b * x + c;
}

能过,但味道不太对。时间复杂度是 O(n log n),而这题其实能做到 O(n)。

关键点不在公式本身,而在二次函数图像。

先看函数开口方向

公式是:

f(x) = ax * x + b * x + c

如果 a > 0,抛物线开口向上,离对称轴越远,函数值越大。 如果 a < 0,抛物线开口向下,离对称轴越远,函数值越小。

而原数组 nums 本身已经升序。

这时候有个很实用的判断:数组两端的元素,往往会产生当前最大值或者最小值。

所以这题的做法其实很像“有序数组平方”那道题,都是双指针。

为什么两端能决定答案

拿 a > 0 举例。

抛物线开口向上,越靠近两边,值越大。 所以此时转换后的最大值,一定更可能出现在 nums[left] 或 nums[right] 里。

那就可以这样做:

  • 左右指针分别指向数组两端
  • 比较两端代入函数后的值
  • 谁大,谁就放到结果数组的末尾
  • 指针往中间收缩

如果 a < 0 呢?

这时候开口向下,两端反而更小。 那就把较小的值放到结果数组开头。

这一步想通,代码就顺了。

publicclassSolution{

publicint[] sortTransformedArray(int[] nums, int a, int b, int c) {
int n = nums.length;
int[] ans = newint[n];

int left = 0;
int right = n - 1;

int idx = a >= 0 ? n - 1 : 0;

while (left <= right) {
int lv = transform(nums[left], a, b, c);
int rv = transform(nums[right], a, b, c);

if (a >= 0) {
if (lv > rv) {
                    ans[idx--] = lv;
                    left++;
                } else {
                    ans[idx--] = rv;
                    right--;
                }
            } else {
if (lv < rv) {
                    ans[idx++] = lv;
                    left++;
                } else {
                    ans[idx++] = rv;
                    right--;
                }
            }
        }

return ans;
    }

privateinttransform(int x, int a, int b, int c){
return a * x * x + b * x + c;
    }
}

拿一组数据走一下

比如:

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

函数是:

f(x) = x^2 + 3x + 5

两端先算:

f(-4) = 9
f(4)  = 33

因为 a > 0,大的先放后面,所以 33 先进结果数组末尾。

接着继续比较,直到收敛。

最后得到:

[3, 9, 15, 33]

整个过程没有额外排序,指针只扫一遍。

这题真正容易卡的地方

不是双指针本身,而是填充方向。

很多人写到一半会乱:

  • a >= 0 时,结果从后往前填
  • a < 0 时,结果从前往后填

这个地方一旦反了,结果要么逆序,要么还得再来一次排序,那就白做了。

还有一个细节,判断里我写的是 a >= 0,不是单纯 a > 0。 因为 a == 0 时,函数退化成一次函数:

f(x) = bx + c

这时候虽然不是抛物线了,但按这个逻辑照样能处理,不需要单独拆分分支。

复杂度

这题优化后的复杂度很干净:

时间复杂度:O(n)
空间复杂度:O(n)

空间这里没法省掉,因为题目本身就要返回新数组。

这题看着是数学题,落到代码上其实还是双指针。 题目里只要出现“原数组有序”,通常都值得先停一下,看看能不能少一次 sort。 这一步想到了,题就已经做完一半了。