本篇主要针对常见排序算法整合简单应用场景,在日常刷题总结过程中学会利用排序思想,如有不足,恳请指正,欢迎交流~
前言
在日常刷题过程中将用到的排序算法思想总结至此,有助于拓展解题思维~
1. 小和问题 [归并排序思想]
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
[1, 3, 4, 2, 5]
1 左边比 1 小的数,没有;
3 左边比 3 小的数,1;
4 左边比 4 小的数,1、3;
2 左边比 2 小的数,1;
5 左边比 5 小的数,1、3、4、 2;
因此小和为 1 + 1 + 3 + 1 + 1 + 3 + 4 + 2 = 16
图解算法
1/3
2/3
3/3
代码实现
merge
public static int merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = 0;
int p1 = left;
int p2 = mid + 1;
int res = 0;
while (p1 <= mid && p2 <= right) {
//求小和
res += arr[p1] < arr[p2] ? (right - p2 + 1) * arr[p1] : 0;
//归并
temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
//当左边元素有剩余
while (p1 <= mid) {
temp[i++] = arr[p1++];
}
//当右边元素有剩余
while (p2 <= right) {
temp[i++] = arr[p2++];
}
//重新拷贝回原始数组对应位置
for (int j = 0; j < temp.length ; j++) {
arr[left + j] = temp[j];
}
return res;
}
mergeSort
public static int mergeSort(int[] arr, int left, int right) {
if (left == right) {
return 0;
}
int mid = left + ((right - left) >> 1);
return mergeSort(arr, left, mid)
+ mergeSort(arr, mid + 1, right)
+ merge(arr, left, mid, right);
}
smallSum
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return mergeSort(arr, 0, arr.length - 1);
}
2. 逆序对问题 [归并排序思想]
算法思想
把要统计逆序对个数的序列 C 沿着中间位置切分成两个子序列 A 和 B,递归地计算子序列 A 和子序列 B 中逆序对的个数并排序,然后合并两个子序列,合并的同时计算所有 (ai, aj) 的数对中逆序对的个数 ( ai 在子序列 A,aj 在子序列 B )。
由于子序列 A 和 B 是已经排好序的,在把 A 和 B 合并到 C 时,按照归并排序的合并过程,每次都挑选两个子序列中最小的元素加入到 C 中。
每次 A 中的元素 ai 被加到 C 中,不会遇到新的逆序,因为 ai 小于子序列 B 中剩下的每个元素,并且 ai 出现在 B 中元素的前面。
但每次 B 中的元素 bj 被加到C中,说明它比子序列 A 中剩下的元素都小,由于 B 中所有元素本来都排在 A 后面,所以 bj 就与 A 中剩下的所有元素都构成逆序对,此时 A 中剩下的元素个数就是与 bj 构成的逆序对的个数。
图解算法
1/2
2/2
伪代码实现
mergeAndCount(A, B)
初始化count= 0,C = 空
while A和B都不为空
令ai和bj分别为A和B中的首元素
if ai <= bj
把ai加入到输出表C中
A = A - {ai}
else
把bj加入到输出表C中
B = B - {bj}
count += A中剩下的元素
endIf
endWhile
if A 为空
把B中剩下元素加入到C
else
把A中剩下元素加入到C
endIf
return 合并结果C和逆序对个数count
sortAndCount ( C )
if L只有1个元素
没有逆序,c1 = c2 = c3 = 0
else
把这个表C均分成两半,A和B
(c1, A) = sortAndCount(A)
(c2, B) = sortAndCount(B)
(c3, C) = mergeAndCount(A, B)
endIf
return (c1 + c2 + c3, C)
代码实现
merge
/**
* 归并同时求逆序对
* @param arr
* @param left
* @param mid
* @param right
* @return 逆序对个数
*/
public static int merge(int[] arr, int left, int mid,int right) {
int[] temp =new int[right - left + 1];
int i = left, j = mid + 1, k = 0, count = 0;
while (i <=mid && j <= right) {
//从mid开始划分成两段数组进行归并并找出逆序对
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else { //当B段小于A段的数时,便找到了逆序对
temp[k++] = arr[j++];
//此时逆序对数为A段剩余元素数量
count += mid - i + 1;
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (i = 0; i < temp.length; i++) {
arr[left + i] = temp[i];
}
return count;
}
mergeSort
/**
* 对于任意的边界L:R求出逆序对
* @param arr
* @param left
* @param right
* @return
*/
public static int mergeSort(int[] arr, int left, int right) {
if (left == right) {
return 0;
}
int mid = left + ((right - left) >> 1);
return mergeSort(arr, left, mid) +
mergeSort(arr, mid + 1, right) +
merge(arr, left, mid, right);
}
reverseSort
public static int reverseSort(int[] arr) {
if (arr == null || arr.length < 1) {
return 0;
}
return mergeSort(arr,0,arr.length-1);
}
3. 堆排序扩展 [堆排序思想]
已知一个几乎有序的数组
,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的 排序算法针对这个数据进行排序 。
算法图解
代码实现
sortedArrDistanceLessK
public void sortedArrDistanceLessK(int[] arr, int k) {
PriorityQueue<Integer> heap = new PriorityQueue<>();
int index = 0;
for (; index < Math.min(arr.length, k); index++) {
heap.add(arr[index]);
}
int i = 0;
for (; index < arr.length; i++, index++) {
heap.add(arr[index]);
arr[i] = heap.poll();
}
while (!heap.isEmpty()) {
arr[i++] = heap.poll();
}
}
4. 荷兰旗问题 [快速排序思想]
枢轴问题
给定一个数组 arr,和一个数 num,请把小于等于 num 的数放在数组的左边,大于 num 的 数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)。
算法图解
代码实现
partition
public static void partition(int[] arr, int left, int right, int target) {
//小于小等于边界
int j = left - 1;
int i = 0;
while (i < arr.length) {
if (arr[i] <= target) {
utils.swap(arr,++j,i++);
} else {
i++;
}
}
}
荷兰国旗问题
给定一个数组 arr,和一个数 num,请把小于 num 的数放在数组的 左边,等于 num 的数放在数组的中间,大于 num 的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度 O(N)。
算法图解
代码实现
Netherlands
public static int[] Netherlands(int[] arr, int left, int right, int target) {
int i = left - 1;
int k = right + 1;
while (left < k) {
if (arr[left] < target) {
utils.swap(arr, ++i, left++);
} else if (arr[left] > target) {
utils.swap(arr, --k, left);
} else {
left++;
}
}
//target边界
return new int[] {i + 1, k - 1};
}
[补充] 二分法应用
在一个有序数组中,找某个数是否存在
代码实现
public static boolean binarySearch(int[] sortedArr, int num) {
if (sortedArr == null || sortedArr.length == 0) {
return false;
}
int left = 0;
int right = sortedArr.length - 1;
int mid = 0;
while (left < right) {
//防止越界
mid = left + ((right - left) >> 1);
if (sortedArr[mid] == num) {
return true;
} else if (sortedArr[mid] > num) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return sortedArr[left] == num;
}
在一个有序数组中,找大于等于某个数最左侧的位置 [重要]
代码实现
public static int nearestIndex(int[] arr,int value) {
int left = 0;
int right = arr.length - 1;
int mid = 0;
int index = -1;
while (left < right) {
mid = left + ((right - left) >> 1);
if (arr[mid] >= value) {
index = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return index;
}
局部最小值问题
情况3
mid比它左边小,比它右边小,所以已经是局部最小,直接返回
情况4
此时arr[0]~arr[1]是下降趋势, arr[mid-1]~arr[mid]是上升趋势,所以在此范围内数组arr一定存在局部最小。去掉mid右边的范围,直接找mid左边。
备注:虽然mid右边也存在局部最小,但是我们只需要返回一个局部最小,所以我们选择直接往mid左边找就可以了。
情况5
此时arr[mid]~arr[mid+1]是下降趋势, arr[n-2]~arr[n-1]是上升趋势,所以在此范围内数组arr一定存在局部最小。去掉mid左边的范围,找mid右边。
情况6
此时arr[0]~arr[1]是下降趋势, arr[mid-1]~arr[mid]是上升趋势,所以在此范围内数组arr一定存在局部最小。去掉mid右边的范围,找mid左边。
代码实现
public static int LocalMin(int[] arr) {
//int ans = 0;
//非法情况
if (arr == null || arr.length == 0) {
return -1;
}
int N = arr.length;
if (N == 1) {
return 0;
}
//情况1
if (arr[0] < arr[1]) {
return 0;
}
//情况2
if (arr[N - 1] < arr[N - 2]) {
return N - 1;
}
int left = 0;
int right = N - 1;
/**
* L:R一定有局部最小
* 保证L~R之间至少有三个数,才能二分
* 若不足三个数直接比较大小
*/
while (left < right) {
int mid = left + ((right - left) >> 1);
if (arr[mid] <= arr[mid - 1] && arr[mid] <= arr[mid + 1]) {
return arr[mid];
} else if (arr[mid] >= arr[mid + 1]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -999;
}