一、小范围排序

  已知一个几乎有序的数组(几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小),请选择一个合适的排序算法针对这个数组进行排序。

1.分析

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

  由于我们不知道这个数组的具体范围,故无法使用桶排序思想(不适用所有范围)。

(2)时间复杂度为O(n^2)的算法:冒泡排序、选择排序、插入排序

  对于冒泡排序、选择排序:这两种算法的排序与数组的原始序列无关,因此时间复杂度仍为O(n^2)。
  对于插入排序:由于每个元素移动的距离可以不超过k,因此在该问题中插入排序的每个元素向前插入的位置不会超过k,因此时间复杂度下降为O(n*k)。

(3)时间复杂度为O(n*log n)的算法:快速排序、归并排序、堆排序

  对于快速排序、归并排序:这两种算法的排序与数组的原始序列无关,因此时间复杂度仍为O(n*log n)。
  堆排序(改进):每个元素移动的距离可以不超过k,因此数组的最小值一定在array[0] ~ array[k-1]中。因此以array[0] ~ array[k-1]建立小顶堆,然后将堆顶元素赋值给array[0],然后从数组中插入array[k]到堆顶,重建小顶堆。如此反复,直到所有元素都插入到小顶堆中。最后数组中的元素插入完后(还剩堆中的k个元素),则将剩余堆中的元素按照堆排序的方式排序即可。对于每个数进入小顶堆后都要进行时间复杂度O(log k)的建堆过程,然后一共要排序n个元素,因此时间复杂度为O(n*log k)。

2.总结

算法 时间复杂度
计数排序、基数排序 O(n) 但是该问题未给出数据范围故不适用所有情况,贸然使用可能导致极大的空间复杂度
冒泡排序、选择排序 O(n^2)
插入排序 O(n*k)
归并排序、快速排序 O(n*logn)
堆排序 O(n*log k)
import java.util.*;

public class ScaleSort {
    public int[] sortElement(int[] A, int n, int k) {
        // write code here

        if(A == null || n <= 1)
            return A;

        int[] heap = getHeapK(A, k);        //获取辅助小顶堆

        //每次将堆顶元素放置于数组前部,并将数组的下一个元素放入堆顶,进行堆调整,如此反复
        for(int i = k; i < n; i++){
            A[i - k] = heap[0];
            heap[0] = A[i];
            adjustMinHeap(heap, 0, k);
        }

        //对剩余的k个元素进行堆排序,每次将堆顶元素置于数组前部
        for(int i = n-k; i < n; i++){
            A[i] = heap[0];
            swap(heap, 0, k-1);
            adjustMinHeap(heap, 0, --k);
        }

        return A;
    }

    private int[] getHeapK(int[] A, int k){
        //对前k个元素建立小顶堆
        int[] heap = new int[k];
        for(int i = 0; i < k; i++){
            heap[i] = A[i];
        }
        for(int i = k/2 - 1; i >= 0; i--){
            adjustMinHeap(heap, i, k);
        }

        return heap;
    }

    private void adjustMinHeap(int[] heap, int current, int length){
        int temp = heap[current];

        for(int i = current*2+1; i < length; i = i*2+1){
            if(i+1 < length && heap[i+1] < heap[i])
                i++;

            if(temp > heap[i]){
                heap[current] = heap[i];
                current = i;
            }
            else{
                break;
            }
        }

        heap[current] = temp;
    }

    private void swap(int[] A, int index1, int index2){
        int temp = A[index1];
        A[index1] = A[index2];
        A[index2] = temp;
    }
}

二、低空间复杂度重复值判断

  请设计一个高效算法,判断数组中是否有重复值。必须保证额外空间复杂度为O(1)。
  本题如果没有空间复杂度限制,应使用哈希表实现,统计每个元素的个数,此时时间复杂度和空间复杂度均为O(n)。
  本题的解法为先排序,后遍历,遍历到相同元素说明有重复值,否则没有。因此问题转为应该使用何种排序算法
  在排序算法中,我们知道堆排序的在空间复杂度符合要求O(1)的情况下时间复杂度相对较低,因此本题应使用堆排序(但要注意要非递归实现,否则空间复杂度就不是O(1)了)

import java.util.*;

public class Checker {
    public boolean checkDuplicate(int[] a, int n) {
        if(a == null || n <= 1){
            return false;
        }

        //堆排序
        for(int i = n/2-1; i >=0; i--){
            adjustMaxHeap(a, i, n);
        }       
        for(int i = n-1; i > 0; i--){
            swap(a, 0, i);
            adjustMaxHeap(a, 0, i);
        }

        //查重
        for(int i = 1; i < n; i++){
            if(a[i] == a[i-1])
                return true;
        }

        return false;

    }

    private void  adjustMaxHeap(int[] a, int current, int length){
        int temp = a[current];

        for(int i = current*2 + 1; i < length; i = i*2+1){
            if(i+1 < length && a[i+1] > a[i])
                i++;

            if(temp < a[i]){
                a[current] = a[i];
                current = i;
            }
            else{
                break;
            }
        }

        a[current] = temp;
    }

    private void swap(int[] a, int index1, int index2){
        int temp = a[index1];
        a[index1] = a[index2];
        a[index2] = temp;
    }
}

三、有序数组合并

  有两个从小到大排序以后的数组A和B,其中A的末端有足够的缓冲空容纳B。请编写一个方法,将B合并入A并排序。
  这道题非常简单,对两个数组分别从后向前遍历,每次比较两个指针指向的元素,将较大者至于A数组末尾(下一次放在末尾的前一个位置,如此反复)。最后所有数组遍历完,返回A即可。

import java.util.*;

public class Merge {
    public int[] mergeAB(int[] A, int[] B, int n, int m) {
        // write code here
        int indexA = n-1;
        int indexB = m-1;
        int mergeIndex = m+n-1;

        while(indexA >= 0 && indexB >= 0){
            A[mergeIndex--] = A[indexA] > B[indexB] ? A[indexA--] : B[indexB--]; 
        }

        while(indexB >= 0){
            A[mergeIndex--] = B[indexB--];
        }

        return A;
    }
}