一、小范围排序题
题意:
所谓的小范围排序题指的是,已知一个几乎有序的数组,几乎有序的意思是,如果把数组排好顺序,每个元素移动的距离不超过k,并且k的大小远远小于数组的长度。问选择什么样的方法好。
分析:
1.时间复杂度为O(n)
计数排序和基数排序,由于桶排序类排序算法的特点,当我们不知道数据的具体范围时,无法使用。所以不考虑。
2.时间复杂度为O(n²)
其中冒泡排序和选择排序的时间复杂度不会根据数组的排列改变,始终都是O(n²)
而插入排序的时间复杂度在小范围排序的情况下可以优化到O(n*k)。
3.时间复杂度为O(n*㏒n)
其中快速排序和归并排序的时间复杂度不会受到影响
其实这一题主要使用的是一种改进后的堆排序算法。时间复杂度能过降到O(n*㏒k)。
思路:
因为排序过后每个元素移动的距离不会超过k,那么意味着最小值一定在0位置到k-1位置中,所以我们可以首先将第0个位置到底k-1个位置的数建一个小根堆,然后将堆顶弹出,再将第k个位置的数放入堆中,调整堆,以此类推。
代码举例:
import java.util.*;
public class ScaleSort {
public int[] sortElement(int[] A, int n, int k) {
// write code here
if (A == null || A.length == 0 || n < k){
return A;
}
//创建小根堆
int[] heap = getKHeap(A, k);
//遍历数组,将k-1位置以后的数一次放入根堆并进行调整
for (int i = k; i < n; i++) {
//将堆顶元素弹出,放到数组中,从0号一直到n号
A[i - k] = heap[0];
//讲下一个元素放入小根堆
heap[0] = A[i];
//调整根堆
heapify(heap, 0, k);
}
//下面的操作,是当循环进行到最后一个元素时,还剩最后一个n-k到n位置的小根堆,单独对这个小根堆进行操作
for (int i = n - k; i < n; i++) {
A[i] = heap[0];
swap(heap, 0, k - 1);
heapify(heap, 0, --k);
}
return A;
}
//创建堆结构,小根堆调整
public void heapify(int[] arr,int index,int heapSize){
//创建堆结构
int left = index*2+1;
int right = index*2+2;
int smallest = index;
//对堆结构进行调整
while(left<heapSize){
if(arr[left]<arr[index]){
smallest = left;
}
if(right<heapSize && arr[right]<arr[index]){
smallest = right;
}
//当最小元素的值位置发生改变时,调整堆结构
if(smallest != index){
swap(arr,smallest,index);
}else{
break;
}
//重新调整标签指向
index = smallest;
left = index*2+1;
right = index*2+2;
}
}
//插入新元素到已有的堆结构中
public void heapInsert(int[] heap,int value,int index){
//获取值
heap[index] = value;
//调整堆结构
while(index != 0){
//定义它的父亲节点
int parant = (index-1)/2;
if(heap[index]<heap[parant]){
swap(heap,parant,index);
index = parant;
}else{
break;
}
}
}
//创建小根堆
public int[] getKHeap(int[] A,int k){
int[] heap = new int[k];
//一次遍历数组,将元素一个个放入根堆中
for (int i = 0; i < k; i++) {
heapInsert(heap, A[i], i);
}
return heap;
}
//创建比较函数
public void swap(int[] arr,int index1,int index2){
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
}
二、重复值判断
题意:
判断数组中重复的数的格式,并且要求空间复杂度为O(1)
分析:
如果这个题没有要求空间复杂度,那么我们使用哈希表来实现的,但是规定了空间复杂度,所以我们需要将数组排序,然后进行遍历,从而获得结果。
思路:
我们可以使用空间复杂度为O(1)的算法入冒泡排序,选择排序,插入排序,希尔排序,堆排序;这个地方我们如果使用到的是堆排序,但是如果堆排序使用递归实现的话,空间复杂度为O(logn)。
代码举例:
import java.util.*;
public class Checker {
public boolean checkDuplicate(int[] a, int n) {
// write code here
if (a == null || n == 0) {
return false;
}
heapSort(a);
for (int i = 1; i < n; i++) {
if (a[i] == a[i - 1]) {
return true;
}
}
return false;
}
//创建堆结构
public void heapSort(int[] arr) {
//将元素插入堆中
for (int i = 0; i != arr.length; i++) {
heapInsert(arr, i);
}
//遍历堆,调整
for (int i = arr.length - 1; i != 0; i--) {
swap(arr, 0, i);
heapify(arr, 0, i);
}
}
//创建堆结构,小根堆调整
public void heapify(int[] arr,int index,int heapSize){
//创建堆结构
int left = index*2+1;
int right = index*2+2;
int smallest = index;
//对堆结构进行调整
while(left<heapSize){
if(arr[left]<arr[index]){
smallest = left;
}
if(right<heapSize && arr[right]<arr[index]){
smallest = right;
}
//当最小元素的值位置发生改变时,调整堆结构
if(smallest != index){
swap(arr,smallest,index);
}else{
break;
}
//重新调整标签指向
index = smallest;
left = index*2+1;
right = index*2+2;
}
}
//插入新元素到已有的堆结构中
public void heapInsert(int[] heap,int index){
//调整堆结构
while(index != 0){
//定义它的父亲节点
int parant = (index-1)/2;
if(heap[index]<heap[parant]){
swap(heap,parant,index);
index = parant;
}else{
break;
}
}
}
//创建比较函数
public void swap(int[] arr,int index1,int index2){
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
}
三、有序数组合并
题意:
现在有两个有序数组,分别为a,和b,此时要将这两个数组合并为一个有序数组。合并后的数组正好可以容纳这两个数组的全部元素。
分析及思路:
这样要求我们首先应该从两个数组的后面向前面比较,因为这样就不会因为插入的顺序影响了比较。
代码举例:
import java.util.*;
public 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 k = m+n-1;
//循环遍历比较大小,插入数组
while(i>=0 && j>=0){
if(A[i]>B[j]){
A[k]=A[i];
i--;
}else{
A[k]=B[j];
j--;
}
k--;
}
//当A中的元素全部比较完,但是B中还有元素时,将B中的元素直接转移到数组中
while(j>=0){
A[k--]=B[j--];
}
return A;
}
}