快速排序及它的變形題型在面試中經常被問到,本文進行一下總結。
快速排序
實現快速排序的關鍵便是partition函數,這個函數的主要功能是把比選擇數字小的數字移到數組的左邊,把比選擇數字大的數字移到數組的右邊。
主要實現分爲以下四步:
第一步:找基準數,並把它放到數組的末尾
第二步:使用small標記劃分區間,初始值爲-1,意味着劃分區間無值
第三步:遍歷數組,將比基準值小的值放入劃分區間
第四步:將選擇的數字也放入劃分區間
代碼如下
public static int partition(int[] arr,int len,int start,int end){
if(arr==null||len<=0||start<0||end>=len){
return -1;
}
//找基準數,並將基準數放到數組的末尾
int index=start+(int)(Math.random()*(end-start+1));
swap(arr,index,end);
//劃分區間
int small=start-1;
for(index=start;index<end;index++){
if(arr[index]<arr[end]){
small++;
if(index!=small){
swap(arr,index,small);
}
}
}
//將選擇的數字也放入到區間
small++;
swap(arr,small,end);
return small;
}
public static void swap(int[] arr,int index1,int index2){
int tmp=arr[index1];
arr[index1]=arr[index2];
arr[index2]=tmp;
}
快排的實現
public void quicksort(int[] data,int start,int end){
if(start==end){
return;
}
int index=partition(arr,start,end);
if(index>start){
quicksort(arr,start,index-1);
}
if(index<end){
quicksort(arr,index+1,end);
}
}
時間複雜度分析,因爲快排也採用分而治之的思想,因此在最優情況下(partition每次劃分的很均勻)時間複雜度爲O(nlog2n),最差的情況下時間複雜度爲n^2;
相關題型
數組中出現次數超過一半的數字
面試題39. 數組中出現次數超過一半的數字
思路一:
將數組排序,然後數組中位數,則爲要求的結果,數組排序的時間複雜度爲O(nlogn);
思路二:
使用partition
class Solution {
public int majorityElement(int[] nums) {
/*
直接找nums.length/2大的數
*/
if(nums==null){
return -1;
}
int len=nums.length;
int middle=len>>1;
int start=0;
int end=len-1;
int index=partition(nums,len,start,end);
while(index!=middle){
if(index>middle){
end=index-1;
index=partition(nums,len,start,end);
}else{
start=index+1;
index=partition(nums,len,start,end);
}
}
int res=nums[middle];
if(!cheackMoreThanHalf(nums,len,res)){
res=0;
}
return res;
}
public int partition(int[] nums,int len,int start,int end){
if(nums==null||len<=0||start<0||end>=len||start>end){
return -1;
}
int index=start+(int)(Math.random()*(end-start+1));
swap(nums,index,end);
int small=start-1;
for(index=start;index<end;index++){
if(nums[index]<nums[end]){
small++;
if(index!=small){
swap(nums,index,small);
}
}
}
small++;
swap(nums,small,end);
return small;
}
public void swap(int[] nums,int index1,int index2){
int tmp=nums[index1];
nums[index1]=nums[index2];
nums[index2]=tmp;
}
public boolean cheackMoreThanHalf(int[] nums,int len,int number){
int times = 0;
for (int i = 0; i <len; i++) {
if (nums[i] == number)
times++;
}
boolean isMoreThanHalf = true;
if (times * 2 <= len) {
isMoreThanHalf = false;
}
return isMoreThanHalf;
}
}
思路三:
摩爾投票法
class Solution {
public int majorityElement(int[] nums) {
int x=0,votes=0;
for(int num:nums){
if(votes==0) x=num;
votes+=num==x?1:-1;
}
return x;
}
}
思路四:使用map記錄出現的次數,不再贅述
最小的k個數
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(arr==null||arr.length<=0||k<=0){
return new int[0];
}
int res[]=new int[k];
int len=arr.length;
int start=0;
int end=len-1;
int index=partition(arr,start,end);//最小的k個數
while(index!=k-1){
if(index>k-1){//比k-1多,在前index中找
end=index-1;
index=partition(arr,start,end);
}else{//比k-1少,從start開始找
start=index+1;
index=partition(arr,start,end);
}
}
for(int i=0;i<k;i++){
res[i]=arr[i];
}
return res;
}
int partition(int[] arr,int start,int end){
if(arr==null||arr.length<=0||start<0||end>=arr.length){
return -1;
}
int index=start+(int)(Math.random()*(end-start+1));
swap(arr,index,end);
int small=start-1;
for(index=start;index<end;index++){
if(arr[index]<arr[end]){
small++;
if(small!=index){
swap(arr,index,small);
}
}
}
small++;
swap(arr,small,end);
return small;
}
public void swap(int[] arr,int i,int j){
int tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
}
查找第k大的數字
215. 數組中的第K個最大元素
題目描述:
在未排序的數組中找到第 k 個最大的元素。請注意,你需要找的是數組排序後的第 k 個最大的元素,而不是第 k 個不同的元素。
class Solution {
public int findKthLargest(int[] nums, int k) {
if(nums==null||nums.length<=0||k<=0||k>nums.length){
return 0;
}
int start=0;
int len=nums.length;
int end=nums.length-1;
int index=partition(nums,start,end);
int target=len-k;
while(index!=target){
if(index<target){
index=partition(nums,index+1,end);
}else{
index=partition(nums,start,index-1);
}
}
return nums[index];
}
int partition(int[] arr,int start,int end){
if(arr==null||arr.length<=0||start<0||end>=arr.length){
return -1;
}
int index=start+(int)(Math.random()*(end-start+1));
swap(arr,index,end);
int small=start-1;
for(index=start;index<end;index++){
if(arr[index]<arr[end]){
small++;
if(small!=index){
swap(arr,index,small);
}
}
}
small++;
swap(arr,small,end);
return small;
}
public void swap(int[] arr,int i,int j){
int tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
}
注意這道題的另一個思路是使用堆來解決。使用一個大小爲k的小頂堆,堆頂維護這最小的元素,當新的元素比堆頂元素還要小,那麼不用管它,否則刪除堆頂元素,把較大的元素加入,這樣堆頂就一直維護着第k個大的元素。
使用java中的優先隊列PriorityQueue實現如下:
public int findKthLargest(int[] nums, int k) {
if(nums==null||nums.length<=0||k<=0){
return 0;
}
PriorityQueue<Integer> priorityQueue=new PriorityQueue<>(k,(a,b)->a-b);
for(int num:nums){
int size=priorityQueue.size();
if(size<k){
priorityQueue.add(num);
}else{
if(num>priorityQueue.peek()){
priorityQueue.poll();
priorityQueue.add(num);
}
}
}
return priorityQueue.peek();
}
}
時間複雜度分析
向大小爲k的堆中添加元素的時間複雜度爲O(logk),我們將重複該操作N次,故總時間負責度爲O(NlogN).
最大的k個數
題目鏈接:打印N個數組整體最大的Top K
有N個長度不一的數組,所有的數組都是有序的,請從大到小打印這N個數組整體最大的前K個數。
例如,輸入含有N行元素的二維數組可以代表N個一維數組。
219, 405, 538, 845, 971
148, 558
52, 99, 348, 691
再輸入整數k=5,則打印:
Top 5: 971, 845, 691, 558, 538
[要求]
時間複雜度爲O(k \log k)O(klogk),空間複雜度爲O(k \log k)O(klogk)
public class NiukePrinttopN {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int row=scanner.nextInt();
int k=scanner.nextInt();
PriorityQueue<Integer> priorityQueue=new PriorityQueue<>(k,(a, b)->a-b);
for(int i=0;i<row;i++){
int number=scanner.nextInt();
for(int j=0;j<number;j++){
int num=scanner.nextInt();
int size=priorityQueue.size();
if(size<k){
priorityQueue.add(num);
}else if(num>priorityQueue.peek()){
priorityQueue.poll();
priorityQueue.add(num);
}
}
}
int res[]=new int[k];
for(int i=0;i<k;i++){
res[i]=priorityQueue.poll();
}
for(int i=k-1;i>0;i--){
System.out.print(res[i]+" ");
}
System.out.print(res[0]);
}
}
轉化爲幾乎有序序列的最小代價
題目描述:
.給一個長度爲偶數n的序列中,前n/2個元素裏面的最大值小於等於後n/2個元素裏的最小值,稱爲“幾乎有序”,可以對序列交換任意多次,兩個不同的需要i,j交換每次的代價是i-j的絕對值。求將給定序列,轉化爲幾乎有序序列的最小代價。
public class Main {
public static void main(String[] args) {
int[] arr={1,5,6,9,2,4};
int len=arr.length;
int start=0;
int end=len-1;
int index=partition(arr,len,start,end);
int target=len>>1;
System.out.println(target);
while(index!=target){
if(index<target){
index= partition(arr,len,index+1,end);
}else{
index =partition(arr,len,start,index-1);
}
}
for(int e:arr){
System.out.print(e+" ");
}
}
public static int partition(int[] arr,int len,int start,int end){
if(arr==null||len<=0||start<0||end>=len){
return -1;
}
//找基準數,並將基準數放到數組的末尾
int index=start+(int)(Math.random()*(end-start+1));
swap(arr,index,end);
//劃分區間
int small=start-1;
for(index=start;index<end;index++){
if(arr[index]<arr[end]){
small++;
if(index!=small){
swap(arr,index,small);
}
}
}
//將選擇的數字也放入到區間
small++;
swap(arr,small,end);
return small;
}
public static void swap(int[] arr,int index1,int index2){
int tmp=arr[index1];
arr[index1]=arr[index2];
arr[index2]=tmp;
}
}