排序算法常见面试题

1. 已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离不超过k,并且k相对于数组长度来说很小。请问选择什么方法对其排序比较好?

时间复杂度为o(n)的排序算法:计数排序 基数排序

但是由于并不知道需要排序的数据的范围,所以不能使用这两种方法。

时间复杂度为o(n2)的排序算法:冒泡排序、选择排序

这两个排序算法与数据原始序列无关,时间复杂度永远都是o(n2)。

插入排序的过程与原始顺序有关,每个元素移动距离不超过K;对于本题来说,插入排序的时间复杂度不会超过为o(n*k)。

时间复杂度为n*logn的排序算法:

快速排序:与数组的原始顺序无关

归并排序:与数组的原始顺序无关

答案:改进后的堆排序,具体解析如下:

因为每个元素移动的距离小于k,所以最小的数据一定位于0-k-1之间;第二小的数据一定位于1-k之间;依此类瑞;

属于所以首先将0-k-1个数建立一个小根堆,此时的堆顶就是整个数组的最小值;将堆顶弹出,放到原来数组的位置

0上,然后将a[k]数据放到堆顶,对这个堆进行调整,之后堆顶的元素就是第二小的元素,将它弹出,放到位置1上,依此类推;

由于每次调整都是在大小为k的小根堆上进行的,所以调整的代价为o(logk),而一共要排列N个数,也就是说要进行N次调整,

所以时间复杂度为o(n*log(k)。具体实现的代码如下:

class ScaleSort {
public:
    vector<int> sortElement(vector<int> A, int n, int k) {
        // write code here\\
        //堆化数组,大小为k的数组的最后一个元素的下边为k-1,它的父节点下标为(k-1-1)/2
        if(n==0||n<k||k<=1)return A;
        vector<int> C;
        for(int s=0;s<k;s++){
            C.push_back(A[s]);
        }
     
        int first=(k-2)/2;
        for(;first>=0;first--){//这个循环非常重要!!!!!必须要从后向前每课子树都要从上到下进行调整
        MinHeapFixdown(C,first,k);
        }
        
        int count=0;
        while(count<n-k){
        A[count]=C[0];
        C[0]=A[count+k];
            count++;
        MinHeapFixdown(C,0,k);//代表从第0个元素开始,对大小为k的堆进行调整,调整成小根堆
        }//上面结束的时候得到的是一个小根堆,但是并不是将小根堆从上到下从左到右输出到B就可以了,而是还是一个一个的输出调整
        //下面需要将A数组中的前k个数据赋值给B
        while(k>=1&&count<n){
            A[count]=C[0];
            count++;
            C[0]=C[k-1];
            k--;
            MinHeapFixdown(C,0,k);
        }
        return A;
    }
    void MinHeapFixdown(vector<int> &C,int i,int n){//这里非常重要!!!!传递参数的时候对于vector类传递的是引用,并不是对象!!如果只传递对象,并不会对A向量做任何改变,因为A和C对象是不同的两个对象!!用引用的话C和A指向的是同一个对象!!!
         int min;
        int current=C[i];
        while(2*i+1<n){
            min=2*i+1;
            if(2*i+2<n){
                if(C[min]>C[2*i+2])
                    min=2*i+2;
            }
            if(current>C[min])
                {
                  C[i]=C[min];
                  i=min;
                
            }else{
                break;
            }
            } 
        C[i]=current;//之所以放在这里是为了如果2*i+1大于n的话,而此时A[i]的位置还空着话,就要在这里进行赋值
        } 
};

2. 给定一个数组,判断数组中是否有重复值,必须保证额外空间复杂度为o(1)。

如果没有空间复杂度的限制,用哈希表实现。哈希表实现,时间复杂度为o(n),空间复杂度为o(n)。

在有空间复杂度限制的条件下,可以先排序,然后判断。这样,重复的数据就会排列在一起。

经典排序算法中,空间复杂度为o(1),我们可以选择堆排序的方式。堆排序经典实现使用了递归的方式。而使用了递归方式的堆排序的空间复杂度为o(logn),这是

因为递归使用了函数栈,栈的大小就是堆的层数。因此需要改出一个非递归方式的堆排序。

class Checker {
public:
     void heapSort(vector<int> &a,int n){
          int i=(n-1-1)/2;
            for(;i>=0;i--){
               MaxHeapFixdown(a,i,n); 
            }
            int k=n-1;
            while(k>=0){
            int temp=a[0];
            a[0]=a[k];
            a[k]=temp;
            MaxHeapFixdown(a,0,k);
                 k--;
            }     
        }
    
           void MaxHeapFixdown(vector<int> &a, int i,int n){
            //从i开始对长度为n的向量a进行调整,调整成为大顶堆
            if(i>=n)
                return;//这个地方的return很重要!!!!
            int left=2*i+1;
            int right=2*i+2;
            int max=i;
            int temp;
            if(left<n&&a[max]<a[left])
                max=left;
            if(right<n&&a[max]<a[right])
                max=right;
            if(max!=i){
                temp=a[i];
                a[i]=a[max];
                a[max]=temp;
                i=max;
                MaxHeapFixdown(a,i,n);//这是用递归的方式进行接下来的调整,既然用了递归,就一定要知道什么时候递归应该停止!!!
            }else{
                return;
            }
               
        }
    
    bool checkDuplicate(vector<int> a, int n) {
 
         heapSort(a,n);
        for(int i=0;i<n-1;i++){
            if(a[i]==a[i+1]){
                return true;
            }
        }
        return false;
    }
};

上面是采用递归的方式进行的堆排序,MaxHeapFixdown里面调用了自己;

class Checker {
public:
     void heapSort(vector<int> &a,int n){
          int i=(n-1-1)/2;
            for(;i>=0;i--){
               MaxHeapFixdown(a,i,n); 
            }
            int k=n-1;
            while(k>=0){
            int temp=a[0];
            a[0]=a[k];
            a[k]=temp;
            MaxHeapFixdown(a,0,k);
                 k--;
            }     
        }
    
           void MaxHeapFixdown(vector<int> &a, int i,int n){
            //从i开始对长度为n的向量a进行调整,调整成为大顶堆
               int max=i;
               int current=a[max];
            while(2*i+1<n){
                if(current<a[2*i+1])
                    max=2*i+1;
                if(2*i+2<n&&a[2*i+2]>a[2*i+1]&&a[2*i+2]>current)
                    max=2*i+2;
                if(i!=max){//这里的判断一定要添加,否则的话如果max不是左右子节点的话,max的位置可能是一个空,不能将它赋值给i,必须将当前的i赋值为current;
                a[i]=a[max];
                i=max; 
                }else{
                    break;
                }
            }
               a[i]=current;
            
        }
    
    bool checkDuplicate(vector<int> a, int n) {
        // 利用非递归的堆排序,所以要借助于循环!
 
         heapSort(a,n);
        for(int i=0;i<n-1;i++){
            if(a[i]==a[i+1]){
                return true;
            }
        }
        return false;
    }
};

上面是采用非递归的方式进行的堆排序,在MaxHeapFixdown中用了循环进行比较!!!还有传递参数的时候注意要用引用!!

3. 把两个有序数组合并到第一个数组。第一个数组空间正好可以容纳两个数组的元素。

采用从后向前覆盖第一个数组,这样可以保证第一个数组中有用的部分不会因为合并而被覆盖掉。

class Merge {
public:
    int* mergeAB(int* A, int* B, int n, int m) {
        // write code here
        int i=n-1;
        int j=m-1;
        int count=n+m-1;
        while(i>=0&&j>=0){
            if(A[i]>B[j]){
                A[count]=A[i];
                count--;
                i--;
            }else{
                A[count]=B[j];
                count--;
                j--;
            }
        }
        while(i>=0){
           A[count]=A[i];
           count--;
           i--; 
        }
        while(j>=0){
           A[count]=B[j];
           count--;
           j--;  
        }
        return A;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值