例1:
给定一个数组,判断数组中是否存在重复的元素,必须保证额外空间复杂度为O(1)
解:
如果没有空间复杂度限制,用哈希表实现,先排序,再判断。
有限制时,应该采用堆排序来实现。
但是经典的堆排序实现使用了递归的方式。递归的方式需要额外的空间(函数栈),所以需要改写成非递归版本。
以下将给出两个版本的代码:
①递归代码
public void heapSort(int[] a) {
// 建立大根堆
buildMaxHeap(a);
for (int i = a.length - 1; i >= 1; i--) {
swap(a[0], a[i]);
adjustDownToUp(a, i, 0);
}
}
private void buildMaxHeap(int[] a) {
int half = a.length / 2;
for (int i = half; i >= 0; i--) {
adjustDownToUp(a, a.length, i);
}
}
private void adjustDownToUp(int[] a, int heapSize, int index) {
// 找出index位置处左右孩子的位置left和right
int left = index * 2 + 1;
int right = index * 2 + 2;
int largest = index;
if (left < heapSize && a[left] > a[index]) {
largest = left;
}
if (right < heapSize && a[right] > a[largest]) {
largest = right;
}
if (index != largest) {
// 交换两个数据
swap(a[index], a[largest]);
adjustDownToUp(a, heapSize, largest);//递归
}
}
②非递归代码
private int[] buildMaxHeap(int[] array) {
for (int i = (array.length - 2) / 2; i >= 0; i--) {
adjustDownToUp(array, i, array.length);
}
return array;
}
private void adjustDownToUp(int[] array, int k, int length) {
int temp = array[k];
for (int i = 2 * k + 1; i < length - 1; i = 2 * i + 1) {
if (i < length && array[i] < array[i + 1]) {
i++;
}
if (temp >= array[i]) {
break;
} else {
array[k] = array[i];
k = i;
}
}
array[k] = temp;
}
// 堆排序
public int[] heapSort(int[] array) {
array = buildMaxHeap(array); // 初始建堆,array[0]为第一趟值最大的元素
for (int i = array.length - 1; i > 1; i--) {
int temp = array[0];
array[0] = array[i];
array[i] = temp;
adjustDownToUp(array, 0, i);
}
return array;
}
下表为排序算法的比较:
例2:
把两个有序数组合并成一个数组,第一个数组空间刚好可以容纳两个数组的元素。
解:
比如数组一为:{2,4,6,null,null,null}
数组二为:{1,3,5}
此时应该拿数组一的最后一个不为空元素与数组二的最后一个元素进行比较,将两者较大的放在最后数组一的最后位置,这样就尽可能的用到有序数组的序列。
例3:
荷兰国旗问题。只包含0,1,2的整数数组进行排序,要求使用交换、原地排序,而不是利用计数排序进行排序。
解:
主要过程与快排划分过程类似,时间复杂度O(N),额外空间复杂度O(1),如下
{0(区)}1,1,0,0,2,1,1,0{2(区)}
{0,0,0(区)}1,1,1 ,1 {2(区)}
如果第一个数是1,就看下一个数,当前数为零时,就将0与0区前面一个数交换位置,并且0区向后扩一个位置,如果当前数为2时,就将2与2区前一个的数交换位置,并且2区向前扩充一个位置,此时换来的2区前一个数并没有遍历过,所以继续考虑换来的数为0,或1,或2 ;这个过程当前位置一直往后走,2区位置往前走,当前位置的和2区位置重合时,停止。
例4:
给定一个二维数组,在行列都排好序的矩阵中找数。如下:
0, 1 , 2, 5
2, 3, 4, 7
4, 4, 4, 7
5, 7, 7, 9;
如果K为7返回true;如果K为6,返回false。
解:
从数组右上角开始找,如果当前数比要找的数大,因为每一列都是有序的所以往左走;如果当前数比要找的数小时,就往下走,因为本行剩下的数肯定不大于要找的数。
例5:
给定一个数组,返回需要排序的最短子数组长度,如下:
[1, 5, 4, 3, 2, 6, 7]
返回4,因为只有[5, 4, 3, 2]需要排序。
解:
①从左往右遍历一个数组,单独一个变量记录遍历过部分的最大值,只需要注意遍历过的最大值大于当前数的值这一种情况,最大值的位置起码在当前数的位置,或更又的位置,我们只记录发生这种情况最右的位置;
②接下来从右往左遍历,记录下遍历过部分的最小的值,同样,我们只关注当前数比最小值大的情况,所以最小值起码在当前数的位置,或更左的位置,我们只需要记录发生这种情况最左的位置;
③需要排序的部分就是在最左的位置到最右位置之间的部分。
最优解时间复杂度O(N),额外空间复杂度O(1)。
例6:
给定一个整型数组arr,返回如果排序之后,相邻两束的最大差值。如下:
某数组排序后为
1, 2, 3, 4, 7, 8, 9
最大差值来自4和7,返回3 。
解:
思想来自桶排序,但不是桶排序;
①首先遍历数组找到最小值和最大值,等量的分成N(数组的元素个数)个区间,每个区间分别对应一个桶,每个数根据自己的区间分别放进各自对应的桶,把最大值放在N+1号桶中;
②所以数进桶之后,1号桶会有最小值,N+1号桶只放最大值,所以必然会出现空桶,同一个桶中的数差值不会超过区间,因此我们不用考虑同一个桶的相邻数,只用考虑桶之间的相邻数;
③接下来比较每一个桶的最小值与下一个非空桶的最大值进行比较,然后记录并更新最大差值;
最优解时间复杂度O(N),额外空间复杂度O(N)。