目录
【题目链接】977.有序数组的平方 - 力扣(LeetCode)
给你一个按 非递减顺序 排序的整数数组
nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
已按 非递减顺序 排序进阶:
请你设计时间复杂度为
O(n)
的算法解决本问题
(一)排序算法的C++实现
解题思路
由题目可知,整数数组有序,但有正有负。排序之前,可以先循环遍历数组,分别将对应位置上的元素变为自己的平方。之后再按照非递减顺序(递增但可以有相同元素)排序。
写法一(插入排序)
排序思路
双层for循环实现插入排序。从第二个元素开始(外层for循环),依次与它前面所有的元素进行比较(内层for循环),如果该元素小于它前面的元素,则插入它前面的元素之前(通过交换位置实现)。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for(int i = 0; i<nums.size(); i++){
nums[i] = nums[i] * nums[i];
}
int temp = 0;
for(int i = 1; i<nums.size(); i++){ //遍历数组的所有元素
for(int j = i; j>0; j--){ //遍历当前元素之前的所有元素
if(nums[j] < nums[j-1]){
temp = nums[j];
nums[j] = nums[j-1];
nums[j-1] = temp;
}
}
}
return nums;
}
};
写法二(冒泡排序)
排序思路
双层for循环实现冒泡排序。从第一个元素开始(外层for循环),依次将整个数组中未放的最大的数移到最后一个可放的位置(内层for循环),如果该元素小于它后面的元素,则交换位置。
将数组看作nums.size()个位置,依次将倒数最大的数放在最后的位置上,已经放过的位置则不能再次放入。(从前往后找,大的放后面)
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for(int i = 0; i<nums.size(); i++){
nums[i] = nums[i] * nums[i];
}
int temp = 0;
for(int i = 0; i<nums.size()-1; i++){ //遍历数组的所有元素
for(int j = 0; j<nums.size()-i-1; j++){ //遍历数组除倒数第i+1大的所有元素
if(nums[j] > nums[j+1]){
temp = nums[j+1];
nums[j+1] = nums[j];
nums[j] = temp;
}
}
}
return nums;
}
};
写法三(数组指针排序)
排序思路
利用题意:因为题目给定的初始数组是有序的(非递减的),则平方后最大值一定出现在两端,依次比较两端的值即可逐步找到整个数组未放元素的最大值。
单层循环实现双指针排序。
(1)定义两个数组指针,指向原始数组的首尾元素;定义一个新的数组存放排序后的结果。
(2)如果左指针所指元素大于右指针,则将左指针元素放入结果数组中,并且左指针右移;
如果右指针所指元素大于左指针,则将右指针元素放入结果数组中,并且右指针左移。
(3)重复第(2)步,将平方后数组两端最大值依次放入结果数组的最后可放入的位置(在结果数组中从后往前放)。(从两端找,大的放后面)
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for(int i = 0; i<nums.size(); i++){
nums[i] = nums[i] * nums[i];
}
std::vector<int> resultList(nums.size());
int i = 0, j = nums.size()-1;
int k = nums.size()-1;
while(k>=0){
if(nums[i] > nums[j]){
resultList[k] = nums[i];
i++;
}else{
resultList[k] = nums[j];
j--;
}
k--;
}
return resultList;
}
};
写法四(快速排序)
排序思路
递归实现快速排序。
(1)定义两个数组指针,指向原始数组的首尾元素;定一个基准key,即右指针所指元素。
(2)每次将比key小的数放在它的左边,将比key大的数放在它的右边(partsort函数实现)。
(3)递归基准两侧,分别看作两个新数组,重复步骤(2)(递归调用partsort函数实现)。
(4)若左指针与右指针重合或越过,则说明子数组中的元素顺序已经排好,调用结束(quicksort函数实现)。
void change(int &a, int &b){
int temp = 0;
temp = a;
a = b;
b = temp;
}
int partsort(vector<int>& a, int left, int right){
int key = right;
while(left < right){
while(left < right && a[left] <= a[key]){
left++;
}
while(left < right && a[right] >= a[key]){
right--;
}
change(a[left], a[right]);
}
change(a[left], a[key]);
return left;
}
void quicksort(vector<int>& a, int left, int right)
{
if(left >= right){
return;
}
int keyi = partsort(a, left, right);
quicksort(a, left, keyi - 1);
quicksort(a, keyi + 1, right);
}
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
for(int i = 0; i<nums.size(); i++){
nums[i] = nums[i] * nums[i];
}
quicksort(nums,0,nums.size()-1);
return nums;
}
};
(二)复杂度分析
时间复杂度(平均) | 空间复杂度(平均) | |
---|---|---|
插入排序 | ||
冒泡排序 | ||
数组指针排序 | ||
快速排序 |
时间复杂度
对数组元素进行平方操作的时间复杂度为。
插入排序、冒泡排序都是用两层for循环,时间复杂度为。数组指针法只需要遍历一次数组,一层while循环,时间复杂度为
。快速排序在平均情况下的时间复杂度为
,但在最坏情况下(例如数组已经有序)时间复杂度为
。
空间复杂度
插入排序、冒泡排序仅使用常数级的额外空间时,空间复杂度为。数组指针排序使用额外数组空间时,空间复杂度为
。快速排序的递归调用会使用栈空间,平均情况下空间复杂度为
,但最坏情况下的空间复杂度为
。
(三)总结
(1)冒泡排序与数组指针排序解题思想相似,都是将数组当作位置,每次将整个数组中第n大的数放在倒数第n的位置上。但数组指针的排序方法仅适用于此题有序数组平方后的排序,利用了题意(具体见写法三的排序思路)。
(2)插入排序、冒泡排序、快速排序适用于任何数组的排序,是通用经典的排序方法。快速排序较难理解,但也是重点方法,且综合效率较高。其他排序方法见下图,目前还未尝试,进一步学习可参考优质博客(原文链接:https://blog.youkuaiyun.com/Edward_Asia/article/details/121419975)。
(3)函数递归可以看作入栈和出栈操作,快速排序思想类似于树的排序,找到基准当作根节点,再对左右子树分别进行排序。
(4)就本题来说,从时间复杂度来看,数组指针法效率最高,而插入排序和冒泡排序效率较低。在空间复杂度方面,插入排序和冒泡排序最节省空间。
学习中,诚挚希望有心者指正和交流,经验或者方法都可。