快速排序及它的变形题型在面试中经常被问到,本文进行一下总结。
快速排序
实现快速排序的关键便是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;
}
}