一、基础 - 归并排序

归并排序思想十分简单,但是利用归并排序的变形,可以解决一些看似较为复杂的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Solution {
//归并排序
public void mergeSort(int[] array) {
if (array == null || array.length < 2) return ;
mergeSort(array,0,array.length - 1);

}

public void mergeSort(int[] array,int left,int right){
if (right == left) return;
int mid = left + ((right - left) >> 1);
mergeSort(array,left,mid);
mergeSort(array,mid + 1,right);
merge(array,left,mid,right);
}

public void merge(int[] array,int left,int mid,int right){
//归并排序特点,开辟一个辅助数组
int[] help = new int[right - left + 1];
int p1 = left;
int p2 = mid + 1;
int i = 0;
while (p1 <= mid && p2 <= right){
help[i++] = array[p1] < array[p2] ? array[p1++] : array[p2++];
}
while (p1 <= mid){
help[i++] = array[p1++];
}

while (p2 <= right){
help[i++] = array[p2++];
}
//排好序数组写回原数组
for (int j : help) {
array[left++] = j;
}
}

public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = new int[]{1,3,4,2,5};
solution.mergeSort(nums);
System.out.println(nums);
}

}

二、拓展

小和问题

问题描述:

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。

例子:

求数组 [1,3,4,2,5] 的小和

分析:

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution {
public int mergeSort(int[] array) {
if (array == null || array.length < 2) return 0;
return mergeSort(array,0,array.length - 1);

}

public int mergeSort(int[] array,int left,int right){
if (right == left) return 0;
int mid = left + ((right - left) >> 1);
int leftSum = mergeSort(array,left,mid);
int rightSum = mergeSort(array,mid + 1,right);
return leftSum + rightSum + merge(array,left,mid,right);
}

public int merge(int[] array,int left,int mid,int right){
//开辟辅助数组
int[] help = new int[right - left + 1];
int p1 = left;
int p2 = mid + 1;
int i = 0;
//小和结果
int result = 0;
while (p1 <= mid && p2 <= right){
//核心步骤,重点在于两边数组都是归并排好序的数组
//因此array[p2]和其右边的值都大于array[p1]
result += array[p1] < array[p2] ? (right - p2 + 1) * array[p1] : 0;
help[i++] = array[p1] < array[p2] ? array[p1++] : array[p2++];
}
while (p1 <= mid){
help[i++] = array[p1++];
}

while (p2 <= right){
help[i++] = array[p2++];
}

for (int j : help) {
array[left++] = j;
}
return result;
}

public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = new int[]{1,3,4,2,5};
System.out.println(solution.mergeSort(nums));
}

}

总结:

小和问题是经典的归并排序变形问题,与此类似的还有逆序对问题

逆序对问题

在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有逆序对(个数)。

仅需改动核心步骤即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//依然递增排序
while (p1 <= mid && p2 <= right){
//核心步骤,重点在于两边数组都是归并排好序的数组
//打印逆序对
if(array[p1] > array[p2]){
//p1右边的都比p2大
for(int i=p1;i<mid;i++){
System.out.println(array[p1]+","+array[p2]);
}
}
//逆序对个数
//result += array[p1] > array[p2] ? (mid - p1 + 1) : 0;
help[i++] = array[p1] < array[p2] ? array[p1++] : array[p2++];
}

逆序对与小和问题本质一样,除此之外,还有荷兰国旗问题,是小和问题的轻微进阶版本

荷兰国旗问题

给定一个数组 arr,和一个数 num,请把小于 num 的数放在数组的左边,等于 num 的数放在数组的中间,大于 num 的数放在数组的右边。要求额外空间复杂度 O (1),时间复杂度 O (N)

返回值含义: 一定会返回一个长度为 2 的数组,等于区域的左边界和右边界(也就是相等区域的边界范围 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {

public int[] partition(int[] arr, int left, int right, int num) {
//less为小于num区的边界,more为大于num的边界,left当做遍历数组的指针
int less = left - 1;
int more = right + 1;
while (left < more){
if (arr[left] < num){
swap(arr,left++,++less);
}else if (arr[left] > num){
swap(arr,--more,left);
}else {
left++;
}
}

return new int[]{less + 1,more - 1};
}

public void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}

public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = new int[]{3,5,0,3,4,5,2,6,9,6,1};
solution.partition(nums,0,nums.length - 1,5);
System.out.println(solution.partition(nums,0,nums.length - 1,5));
}
}

参考:

【1】认识认识 O (NlogN) 的排序 | 左程云算法笔记