[LeetCode 排序数组] | 刷题打卡
一直有刷题习惯,最近才看到掘金举办了刷题活动,特来参加!此题为第14题。不得不说掘金的主题就是漂亮呀!赞。
这个题目不仅仅作为练习排序代码。
这道题为参加掘金活动的最后一题,对于这14篇文章来说,更像是一个收尾,一个大型题目练习。
此题目的意义为结合第11、12、13、14一起看,领会双指针的综合应用:双指针+三指针+划分思想+场景演练
.
本文正在参与掘金团队号上线活动,点击 查看大厂春招职位
一、题目描述:
给你一个整数数组 nums,请你将该数组升序排列。
示例 1:
输入:nums = [5,2,3,1] 输出:[1,2,3,5] 示例 2:
输入:nums = [5,1,1,2,0,0] 输出:[0,0,1,1,2,5]
二、思路分析:
划分思想网上有很多介绍,我通过一段我最喜欢的代码,来介绍划分思想
划分代码
class ArrayPar{
private long[] theArray;
private int nElems;
//---------------------------
public ArrayPar(int max){
theArray=new long[max];
nElems=0;
}
//---------------------------
public void insert(long value){
theArray[nElems] =value;
nElems++;
}
//----------------------------
public int size(){
return nElems;
}
//----------------------------
public void display(){
System.out.print("A=");
for(int j=0;j<nElems;j++)
System.out.print(theArray[j]+" ");
System.out.println(" ");
}
//----------------------------
public int partitionIt(int left,int right,long pivot){
int leftPtr=left-1;
int rightPtr=right+1;
while(true)
{
while(leftPtr<right && theArray[++leftPtr]<pivot)//找出更大一个
;//no
while(rightPtr>left &&theArray[--rightPtr]>pivot)
;//no
if(leftPtr>=rightPtr) //如果超过了规定的值,就跳出,执行划分
break;
else //没有超过规定的值,交换元素下标
swap(leftPtr,rightPtr);
}//end while
return leftPtr;
}//end partitionIt()
//--------------------------------------------
public void swap(int dex1,int dex2)
{
long temp;
temp=theArray[dex1];
theArray[dex1]=theArray[dex2];
theArray[dex2]=temp;
}//end swap();
}//end class
class PartitionApp
{
public static void main(String[] args)
{
int maxSize =16;
ArrayPar arr;
arr=new ArrayPar(maxSize);
for(int j=0;j<maxSize;j++)
{
long n=(int)(Math.random()*199);
arr.insert(n);
}
arr.display();
long pivot=99;//枢纽值,
System.out.println("Pivot is "+pivot);
int size=arr.size();
int partDex=arr.partitionIt(0,size-1,pivot);
System.out.println("Partition is at index "+partDex);
arr.display();
}//end app
}
划分工作原理
-
初始化
划分算法由两个指针
leftPtr\rightPtr
,开始工作,这里的指针是指数据项leftPtr
指向第一个数据项的左边1位rightPtr
指向最后一个数据项的右边1位 在双指针工作之前,它俩分别++
、--
-
停止和交换
当
leftPtr
遇到比pivot
小的元素,则继续右移,遇到比pivot
大的元素,则停下来跳出循环 当rightPtr
遇到比pivot
大的元素,则继续左移,发现比pivot
小的元素,则停下来跳出循环 两个内存while循环
,一个用于leftPtr
,一个用于rightPtr
当两个内循环结束的时候,两个指针正处于一个不合适的位置,交换两个位置的元素 ?> 明白两个内循环while
的作用吗?找到与pivot
相比不合适的位置,并交换 -
处理异常数据
什么是异常? 如果所有数据都小于
pivot
,leftPtr
会右移遍历整个数组,最后越界,类似的情况也会发生在rightPtr
上。 为了避免数组越界,右移的时候leftPtr<right
左移的时候,rightPtr>left
-
划分算法的效率
时间复杂度为
三、AC 代码:
基本快速排序
快速排序大部分时候时间复杂度为 当数组为逆序的时候,每次以第一个元素为pivot,时间复杂度将退化到 快速排序的优化思路,是找到合适的pivot,人们已经设计出更好的pivot选择法:
- 三数取中法:[left,pivot,right]取中位数
完整的快速排序ADT设计在我的这篇文章这里
class ArrayIns
{
private long[] theArray;
private int nElems;
//-------------------------------
public ArrayIns(int max)
{
theArray=new ArrayIns(max);
nElems=0;
}
//--------------------------------
public void insert(long value)
{
theArray[nElesm]=value;
nElems++;
}
//--------------------------------
public void display()
{
System.out.print("A=");
for(int j=0;j<nElems;j++)
{
System.out.print(theArray[j]+" ");
System.out.println("");
}
}
//---------------------------------
public void quickSort() //被main函数调用
{
recQuickSort(0,nElems-1);
}
//---------------------------------
public void recQuickSort(int left,int right)
{
int size=right-left+1;
if(size<=3)
manulSort(left,right); //如果数据项个数小,正常排序
else //数据项个数多,进行快速排序
{
long median=mediaOf3(left,right); //三数取中
int partition=partitionIt(left,right,median);
recQuickSort(left,partition-1); //划分
recQuickSort(partion+1,right); ///划分
}
}//end recQuicSort()
//---------------------------------------
public long medianOf3(int left,int right)
{
int center=(left+right)/2;
if(theArray[left]>theArray[center])
swap(left,center);
if(theArray[left]>theArray[right])
swap(left,right);
if(theArray[center]>theArray[right])
swap(center,right);
swap(center,right-1); //把枢纽值放在右边
return theArray(right-1); //返回中值
}
//----------------------------------------
public void swap(int dex1,int dex2)
{
long temp;
temp=theArray[dex1];
theArray[dex1]=theArray[dex2];
theArray[dex2]=temp;
}
//-----------------------------------------
public int partitionIt(int left,int right,long pivot)
{
int leftPtr=left;
int rightPtr=right-1; //枢纽左边的值
while(true)
{
while(theArray[++leftPtr]<pivot)
;//no
while(theArray[--rightPtr]>pivot)
;//no
if(leftPtr>=rightPtr)
break;
else
swap(leftPtr,rightPtr);
}//end while
swap(leftPtr,right-1); //重新存储枢纽的值
return leftPtr;
}//end partitionIt()
//------------------------------------------
public void manualSort(inr left,int right)
{
int size=right-left+1;
if(size<=1) //不用排序
return;
if(size==2)
{
if(theArray[left]>theArray[right])
swap(left,right);
return
}else //size==3
{
if(theArray[left]>theArray[right-1])
swap(left,right-1);
if(theArray[left]>theArray[right])
swap(left,right);
if(theArray[right-1]>theArray[right])
swap(right-1,right);
}
}//end manualSort()
}//end class
四、总结:
对于编程的总结,我的收获是设计代码的时候要考虑抽象ADT设计,考虑一个类对外暴露的接口有哪些,然后再考虑如何实现。
对于算法思想的总结,我的收获是双指针算法不是单独存在的,可以跟实际问题相结合,如排序,如三角形计数等。
对于这一道题的总结,我的收获是6个关键点:
- 递归基,start>=end,则退出
- pivot初始化,可以选择中点,也可以选择偏移一点
- 两个while内循环,第一支都是left<=right ,第二个判断条件依次为
nums[left]<pivot
,nums[right]>pivot
- 交换left和right指向的元素
- 递归start,right
- 递归left,end
要记住代码很不容易,记住算法思想则容易许多,加油!