找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4445

积分

1

好友

607

主题
发表于 1 小时前 | 查看: 2| 回复: 0

在网上看到一个帖子,有人吐槽自己的职场“滑铁卢”:为了争取更高薪资,面试时硬把不到一万的月薪说成了两万五,想冲击三万。结果公司真给了offer,却在入职后要求提交一年薪资流水。这下牛皮吹破了,回旋镖直接打在了自己身上。

社交媒体用户分享虚报薪资经历截图

想通过面试抬价的心情可以理解,打工人谁不想多挣点?但从一万吹到两万五,这就不是适度包装了,简直是给自己开了美颜十级滤镜。当然,公司这边操作也不算地道,真有疑虑就该在发offer前核实清楚,而不是等人入职了再掀桌子。

说到底,求职谈薪可以策略性上浮,但话不能太飘。否则最后工作没稳住,还得自己跑去仲裁,纯纯是给生活平白加了个困难副本。对于正在准备面试求职的朋友来说,这算是个生动的反面教材。

聊完八卦,进入正题。下面是一道与之画风迥异但值得琢磨的算法题:有序转化数组

我第一次看到这题时,直觉思路很简单:先把数组里每个数都代入公式 f(x) = ax² + bx + c 计算,然后再整体排个序。

代码写起来不复杂,但题目特意给出了原数组有序这个条件,基本就是在提醒你:别把这个现成的优势浪费掉。

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

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

private int calc(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 为例。
抛物线开口向上,越靠近两端的输入 x,其函数值 f(x) 越大。所以,当前转化后序列的最大值,必然出现在 nums[left]nums[right] 的转化结果中。

那么策略就可以是:

  • 左右指针 leftright 分别指向数组两端。
  • 比较两端值代入函数后的结果。
  • 谁的值更大,谁就放到结果数组的当前末尾。
  • 移动贡献了较大值的那个指针(left++right--)。

如果 a < 0 呢?
这时候开口向下,两端的输入 x 转化出的函数值反而更小。策略就反过来:把较小的值放到结果数组的当前开头。

想通了这一点,代码就顺理成章了:

public class Solution {
    public int[] sortTransformedArray(int[] nums, int a, int b, int c) {
        int n = nums.length;
        int[] ans = new int[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;
    }

    private int transform(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 先放入结果数组末尾 ans[3]right 指针左移。
接着比较 f(-4)=9f(2)=1515 更大,放入 ans[2]left 指针右移(此时指向-2)。
依此类推,最终得到有序结果:

[3, 9, 15, 33]

整个过程没有额外的排序步骤,双指针只扫描了一遍数组。

这道题真正容易卡住的地方,不是双指针的比较,而是结果数组的填充方向
很多人写到一半会混乱:

  • a >= 0 时,结果需要从后往前填(因为先确定的是最大值)。
  • a < 0 时,结果需要从前往后填(因为先确定的是最小值)。
    这个地方一旦弄反,结果要么是逆序的,要么就白优化了,还得再来一次排序。

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

f(x) = bx + c

虽然此时不再是抛物线,但按上述逻辑(a>=0 分支)依然能正确处理,不需要单独拆分分支。

复杂度分析
优化后的复杂度非常清晰:

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

空间复杂度这里无法优化,因为题目要求返回一个新的数组。

这道题表面看是数学题,落到代码实现上,核心还是双指针技巧。题目中只要出现“原数组有序”这个条件,通常都值得先停下来思考一下,看看能不能避免那一次 O(n log n) 的排序。这一步想到了,题就做完了一半。更多有趣的算法技巧和讨论,欢迎来云栈社区交流分享。




上一篇:FrankenPHP 正式支持原生Windows运行,性能提升显著并兼容Worker模式
下一篇:Paperclip 开源平台:如何用 Node.js 构建你的 AI Agent 公司与管理多智能体协作
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-15 09:48 , Processed in 0.442514 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表