[LeetCode 排序数组] | 刷题打卡

209 阅读4分钟

[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
}

划分工作原理

  1. 初始化

    划分算法由两个指针leftPtr\rightPtr,开始工作,这里的指针是指数据项 leftPtr指向第一个数据项的左边1位 rightPtr指向最后一个数据项的右边1位 在双指针工作之前,它俩分别++--

  2. 停止和交换

    leftPtr遇到比pivot小的元素,则继续右移,遇到比pivot大的元素,则停下来跳出循环 当rightPtr遇到比pivot大的元素,则继续左移,发现比pivot小的元素,则停下来跳出循环 两个内存while循环,一个用于leftPtr,一个用于rightPtr 当两个内循环结束的时候,两个指针正处于一个不合适的位置,交换两个位置的元素 ?> 明白两个内循环while的作用吗?找到与pivot相比不合适的位置,并交换

  3. 处理异常数据

    什么是异常? 如果所有数据都小于pivotleftPtr会右移遍历整个数组,最后越界,类似的情况也会发生在rightPtr上。 为了避免数组越界,右移的时候leftPtr<right 左移的时候,rightPtr>left

  4. 划分算法的效率

    时间复杂度为O(N)O(N)

三、AC 代码:

基本快速排序

快速排序大部分时候时间复杂度为O(nlong)O(nlong) 当数组为逆序的时候,每次以第一个元素为pivot,时间复杂度将退化到O(n2)O(n^2) 快速排序的优化思路,是找到合适的pivot,人们已经设计出更好的pivot选择法:

  1. 三数取中法:[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个关键点:

  1. 递归基,start>=end,则退出
  2. pivot初始化,可以选择中点,也可以选择偏移一点
  3. 两个while内循环,第一支都是left<=right ,第二个判断条件依次为nums[left]<pivot,nums[right]>pivot
  4. 交换left和right指向的元素
  5. 递归start,right
  6. 递归left,end

要记住代码很不容易,记住算法思想则容易许多,加油!