LeetCode 977. 有序数组的平方(JS代码演示)

148 阅读3分钟

在日常的算法学习中,双指针技巧是非常常见且实用的思路。今天我们就以 LeetCode 第 977 题「有序数组的平方」为例,详细讲解如何用双指针高效解决问题,并带你体会算法优化的乐趣。

题目描述

给定一个按非递减顺序排序的整数数组 nums,数组中的每个元素都可以是负数、零或正数。请你返回一个新的数组,数组中的每个元素是原数组对应元素的平方,并且按非递减顺序排序。

示例:

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]

暴力解法

最直接的思路是:先将每个元素平方,然后对结果数组进行排序。代码实现非常简单:

var sortedSquares = function(nums) {
    return nums.map(x => x * x).sort((a, b) => a - b);
};

这种方法虽然能解决问题,但时间复杂度为 O(nlogn),因为排序的开销较大。有没有更优雅高效的办法呢?

双指针法:O(n) 的线性解法

观察题目,我们发现原数组已经是有序的,但由于存在负数,平方后顺序可能会被打乱。例如,-4 的平方比 3 的平方大。因此,平方后的最大值一定出现在数组的两端(最左或最右)。

基于这个特点,我们可以用「双指针」法:

  1. 定义两个指针,i 指向数组头部(最左),j 指向数组尾部(最右)。
  2. 新建一个结果数组 res,从后往前填充(因为每次比较的最大平方值应该放在末尾)。
  3. 每次比较 nums[i]nums[j] 的平方,谁大就把谁的平方放到 res 的末尾,并移动对应指针。
  4. 重复上述过程,直到两个指针相遇。

977.有序数组的平方.gif

代码如下:

var sortedSquares = function(nums) {
    const n = nums.length;
    const res = new Array(n);
    let i = 0, j = n - 1, k = n - 1; 

    while(i <= j) {
        const left = nums[i] * nums[i];
        const right = nums[j] * nums[j];
        if (left > right) {
            res[k--] = left;
            i++;
        } else {
            res[k--] = right;
            j--;
        }
    }
    return res;
};

代码详解

  • i = 0, j = n - 1:分别指向数组的头和尾。
  • k = n - 1:结果数组的末尾索引。
  • 每次循环比较头尾的平方,较大的放到 res[k],然后移动指针和 k
  • 直到 i > j,所有元素都已放入结果数组。

为什么要从后往前填?

因为平方后的最大值一定在两端产生,所以我们每次都把最大值放到结果数组的最后面,避免了排序的需要。

时间复杂度分析

  • 整个过程只需遍历一遍数组,时间复杂度为 O(n)。
  • 空间复杂度为 O(n),用于存放结果数组。

总结

这道题充分体现了双指针的巧妙应用。通过分析数据的特点,我们可以将原本需要排序的 O(nlogn) 问题,优化为 O(n) 的线性时间复杂度。类似的技巧在处理有序数组、链表等问题时非常常见,值得大家多加练习和体会。

希望这篇文章能帮助你更好地理解双指针的思想,也欢迎你将这份技巧应用到更多的算法题中!