手撕算法——快速排序及相关题型

快速排序及它的变形题型在面试中经常被问到,本文进行一下总结。

快速排序

实现快速排序的关键便是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;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值