大家好!今天来分享力扣第 977 题 "有序数组的平方" 的解题思路。这道题看似基础,但想写出高效解法需要结合数组的特性做巧思 ------ 我们从暴力法的局限出发,一步步推导到最优的双指针解法。
一、题目描述
给你一个按非递减顺序排序 的整数数组 nums,返回每个数字的平方组成的新数组,要求新数组也按非递减顺序排序。
示例:
-
输入:
nums = [-4,-1,0,3,10] -
输出:
[0,1,9,16,100] -
输入:
nums = [-7,-3,2,3,11] -
输出:
[4,9,9,49,121]
二、问题分析:暴力法的局限
首先想到的是暴力解法:先遍历数组计算每个元素的平方,再对结果排序。
暴力法代码如下:
cpp
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res;
for (int num : nums) res.push_back(num * num);
sort(res.begin(), res.end());
return res;
}
暴力法的优点是简单直观,但缺点也很明显:时间复杂度由排序操作主导,达到 O (n log n)(n 为数组长度)。当 n 很大时(比如 10^6 级别),排序的开销会显著拉低效率。
有没有办法避开排序,直接得到有序结果?
三、核心思路:双指针法的妙用
观察原数组的特性:数组是有序的,但负数的平方可能成为最大值 (比如 - 4 的平方 16 比 3 的平方 9 大)。因此,平方后的数组最大值一定出现在原数组的两端(要么最左,要么最右)。
基于这个发现,我们可以用双指针法从两端向中间遍历:
- 用指针
i指向数组头部,指针j指向数组尾部; - 用指针
k指向结果数组的末尾(从后往前填充最大值); - 比较
nums[i]²和nums[j]²,将较大值放入newNums[k],并移动对应的指针(i++或j--),同时k--; - 重复上述步骤,直到
i > j。
四、双指针法代码实现与解读
结合这个思路,我们写出最优解代码(也是题目中给出的解法):
cpp
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> newNums(nums.size()); // 初始化结果数组,大小与原数组一致
int k = nums.size() - 1; // 结果数组的填充指针(从末尾开始)
// 双指针:i指向头部,j指向尾部
for (int i = 0, j = nums.size() - 1; i <= j; ) {
if (nums[i] * nums[i] >= nums[j] * nums[j]) {
newNums[k--] = nums[i] * nums[i]; // 头部平方更大,放入结果末尾
i++; // 头部指针右移
} else {
newNums[k--] = nums[j] * nums[j]; // 尾部平方更大,放入结果末尾
j--; // 尾部指针左移
}
}
return newNums;
}
};
代码解读:
- 初始化结果数组 :
newNums的大小与原数组相同,避免动态扩容的开销; - 双指针遍历 :
i和j分别从两端向中间靠拢,每次处理当前最大的平方值; - 从后往前填充 :
k指针从结果数组末尾开始,保证每次放入的是当前最大的平方值,最终结果自然有序。
五、复杂度分析
- 时间复杂度:O (n)。仅需遍历数组一次,每个元素处理一次;
- 空间复杂度:O (n)。用于存储结果数组(若允许修改原数组,空间复杂度可优化为 O (1),但题目要求返回新数组)。
这是最优复杂度 ------ 因为至少需要遍历一次数组计算平方,时间复杂度不可能低于 O (n)。
六、总结
这道题的关键是利用原数组的有序性,意识到平方后的最大值出现在数组两端,从而用双指针法 "跳过排序步骤"。这种思路不仅适用于本题,还广泛应用于:
- 两数之和 II(有序数组);
- 合并两个有序数组;
- 反转字符串中的元音字母等。
掌握 "有序数组 + 双指针" 的组合技巧,能大幅提升数组类题目的解题效率。如果有其他思路或疑问,欢迎评论交流!