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