给你一个按 非递减顺序 排序的整数数组 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)
的算法解决本问题
解法
1.暴力解法
先用一个for循环把数组中的元素平方一下再放回原位置;
再对新数组进行非递减排序,顺便rolll一下排序方法的底层实现
1.1 冒泡排序 时间复杂度O(n2)
class Solution {
public int[] sortedSquares(int[] nums) {
for(int i=0;i<nums.length;i++){
nums[i]*=nums[i];
}
//冒泡排序:效率低,实现简单 时间复杂度O(n2)
//每一趟将待排序序列中最大元素移到最后,剩下的为新的待排序序列。重复上述步骤直到排完所有元素
int temp=0;
for(int i=0;i<nums.length-1;i++){
for(int j=0;j<nums.length-1-i;j++){
if(nums[j]>nums[j+1]){
temp=nums[j];
nums[j]=nums[j+1];
nums[j+1]=temp;
}
}
}
return nums;
}
}
1.2 选择排序 时间复杂度O(n2)
class Solution {
public int[] sortedSquares(int[] nums) {
int size=nums.length;
for(int i=0;i<size;i++){
nums[i]*=nums[i];
}
//选择排序:效率低,容易实现 时间复杂度O(n2)
//在要排序的一组数组中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的雨第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止
int temp=0;
for(int i=0;i<size;i++){
int k=i;
//选出第i+1轮最小的一个数组元素,下标记作k
for(int j=size-1;j>i;j--){
if(nums[j]<nums[k]){
k=j;
}
}
//将nums[i]与nums[k]的值交换
temp=nums[i];
nums[i]=nums[k];
nums[k]=temp;
}
return nums;
}
}
1.3 插入排序 时间复杂度O(n2)
class Solution {
public int[] sortedSquares(int[] nums) {
int size=nums.length;
for(int i=0;i<size;i++){
nums[i]*=nums[i];
}
//插入排序:时间复杂度O(n2)
//从第一个元素开始,该元素可以认为已经被排序;取出下一个元素,在已经排序的元素序列从后向前扫描
//如果该元素(已排序)大于新元素,将该元素移到下一位置;重复步骤,直到找到已排序的元素小于或者等于新元素的位置后,将新元素插入到该位置中
int temp=0;
int j=0;
for(int i=0;i<size;i++){
temp=nums[i];
//假如temp比前面的值小,则将前面的值后移
for(j=i;j>0 && temp<nums[j-1];j--){
nums[j]=nums[j-1];
}
//因为temp比前面的值小,将temp赋值给前面的值
nums[j]=temp;
}
return nums;
}
}
1.4 希尔排序 时间复杂度O(n2)
根据需求,如果你想要结果从大到小排列,它会首先将数组进行分组,然后将较大值移到前面,较小值移到后面,最后将整个数组进行插入排序,这样比起一开始就用插入排序减少了数据交换和移动的次数,可以说希尔排序是加强版的插入排序。
拿数组5, 2, 8, 9, 1, 3,4来说,数组长度为7,当increment为3时,数组分为两个序列5,2,8和9,1,3,4,第一次排序,9和5比较,1和2比较,3和8比较,4和比其下标值小increment的数组值相比较。此例子是按照从大到小排列,所以大的会排在前面,第一次排序后数组为9, 2, 8, 5, 1, 3,4第一次后increment的值变为3/2=1,此时对数组进行插入排序,实现数组从大到小排列
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
操作方法:
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
class Solution {
public int[] sortedSquares(int[] nums) {
for(int i=0;i<nums.length;i++){
nums[i]*=nums[i];
}
shellSort(nums);
return nums;
}
public void shellSort(int[] data){
int j=0;
int temp=0;
//每次将步长缩短为原来的一半
for(int increment=data.length/2; increment>0; increment/=2){
for(int i=increment;i<data.length;i++){
temp=data[i];
for(j=i; j>=increment; j-=increment){
if(temp<data[j-increment]){//如果想从大到小排,仅需修改这里
data[j]=data[j-increment];
}else{
break;
}
}
data[j]=temp;
}
}
}
}
1.5 快速排序 时间复杂度O(nlogn)
被认为是同数量级的排序方法中平均性能最好的
原理: 使用分治法来把一个串分为两个子串,具体算法描述如下
1. 从数列中挑出一个元素,称为“基准”,基准值的选取可以有多种形式,例如中间数或随机数,分别会对算法的复杂度产生不同的影响。
2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这一步称为分区操作。
3. 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序
//方法一
class Solution {
public int[] sortedSquares(int[] nums) {
int size=nums.length;
for(int i=0;i<size;i++){
nums[i]*=nums[i];
}
quickSort(nums, 0, size-1);
return nums;
}
public void quickSort(int[] data, int low, int high){
int i, j, temp, t;
if(low > high){ //递归退出条件
return;
}
i=low;
j=high;
temp=data[low]; //temp就是基准位
while(i<j){
//找出右边小于基准值temp的数
while(temp<=data[j] && i<j){
j--;
}
//找出左边大于基准值temp的数
while(temp>=data[i] && i<j){
i++;
}
//把两数交换
if(i<j){
t=data[j];
data[j]=data[i];
data[i]=t;
}
}
//最后将基准位与i和j相等位置的数字交换
data[low]=data[i];
data[i]=temp;
//递归调用左半数组
quickSort(data,low,j-1);
//递归调用右半数组
quickSort(data,j+1,high);
}
}
//方法二
class Solution {
public int[] sortedSquares(int[] nums) {
int size=nums.length;
for(int i=0;i<size;i++){
nums[i]*=nums[i];
}
Arrays.sort(nums); //使用java.util.Arrays类,Arrays.sort()快速排序
return nums;
}
}
1.6 归并排序 时间复杂度O(nlogn)
class Solution {
public int[] sortedSquares(int[] nums) {
int size=nums.length;
for(int i=0;i<size;i++){
nums[i]*=nums[i];
}
//时间复杂度O(nlogn)
sort(nums, 0, size-1);
return nums;
}
//归并排序是指两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列
public int[] sort(int[] nums,int low, int high){
int mid=(low+high)/2;
if(low<high){
//左边
sort(nums,low,mid);
//右边
sort(nums,mid+1,high);
//左右归并
merge(nums, low, mid, high);
}
return nums;
}
//将数组中low到high位置的数进行排序
public void merge(int[] nums, int low, int mid, int high){
int[] temp=new int[high-low+1];
int i=low; //左指针
int j=mid+1; //右指针
int k=0;
//把较小的数先移到新数组中
while(i<=mid && j<=high){
if(nums[i]<nums[j]){
temp[k++]=nums[i++];
}else{
temp[k++]=nums[j++];
}
}
//把左边的数移入数组
while(i<=mid){
temp[k++]=nums[i++];
}
//把右边剩余的数移入数组
while(j<=high){
temp[k++]=nums[j++];
}
//把新数组中的数覆盖nums数组
for(int k2=0; k2<temp.length; k2++){
nums[k2+low]=temp[k2];
}
}
}
1.7 堆排序 时间复杂度O(nlog2n)
实例 O(nlog2n)时间复杂度同O(nlogn)在算法里是一样的,算法里面底数默认为2
初始序列:46,79,56,38,40,84
建堆:
交换,从堆中踢出最大数
依次类推:最后堆中剩余的最后两个结点交换,踢出一个,排序完成。
class Solution {
public int[] sortedSquares(int[] nums) {
int size=nums.length;
for(int i=0;i<size;i++){
nums[i]*=nums[i];
}
//循环建堆
for(int i=0; i<size-1;i++){
//建堆
buildMaxHeap(nums,size-1-i);
swap(nums,0,size-1-i);
}
return nums;
}
/**堆排序是一种树形选择排序,是对直接选择排序的有效改进
堆排序有两个过程:一是建立堆,二是堆顶与堆的最后一个元素交换位置。
所以堆排序由两个函数组成:一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数
**/
//对data数组从0到lastIndex建大顶堆
public void buildMaxHeap(int[] data, int lastIndex){
//从lastIndex处节点(最后一个节点)的父节点开始
for(int i=(lastIndex-1)/2; i>=0; i--){
//k保存正在判断的节点
int k=i;
//如果当前k节点的子节点存在
while(k*2+1 <= lastIndex){
//k节点的左子节点的索引
int biggerIndex=2*k+1;
//如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在
if(biggerIndex<lastIndex){
//如果右子节点的值较大
if(data[biggerIndex]<data[biggerIndex+1]){
//biggerIndex总是记录较大子节点的索引
biggerIndex++;
}
}
//如果k节点的值小于其较大的子节点的值
if(data[k]<data[biggerIndex]){
//交换它们
swap(data,k,biggerIndex);
//将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值
k=biggerIndex;
}else{
break;
}
}
}
}
//交换
private void swap(int[] data, int i, int j){
int tmp=data[i];
data[i]=data[j];
data[j]=tmp;
}
}
2. 双指针法
注意点:
使用left, right两个指针,left一定要从0开始left++,right一定要从nums.length-1开始right--。
这样即使原数组有负数存在,给数组元素平方后的新数组排序时,通过比较nums[left]*nums[left]和nums[right]*nums[right]的大小,第一轮也能选出最大的那个数。
//使用while循环
class Solution {
public int[] sortedSquares(int[] nums) {
int left=0;
int right=nums.length-1;
int[] result=new int[nums.length];
int index=right;
while(left<=right){
if(nums[left]*nums[left] > nums[right]*nums[right]){
result[index--]=nums[left]*nums[left++];
}else{
result[index--]=nums[right]*nums[right--];
}
}
return result;
}
}
//或使用for循环
class Solution {
public int[] sortedSquares(int[] nums) {
int n=nums.length-1;
int[] result = new int[nums.length];
int left = 0;
int right = n;
int k = nums.length-1;
for (int i = 0; i <= n; i++) {
if(nums[left]*nums[left]<=nums[right]*nums[right]){
result[k--]=nums[right]*nums[right--];
}else if(nums[left]*nums[left]>nums[right]*nums[right]){
result[k--]=nums[left]*nums[left++];
}
}
return result;
}
}