归并排序介绍
- 整体是递归,左边排好序+右边排好序+merge让整体有序
- 让其整体有序的过程里用了排外序方法
- 利用master公式来求解时间复杂度
- 当然可以用非递归实现
归并排序复杂度
- 根据
T(N) = 2*T(N/2) + O(N^1)可知时间复杂度为O(N*logN) - merge过程需要辅助数组,所以额外空间复杂度为
O(N) - 归并排序的实质是把比较行为变成了有序信息并传递,比
O(N^2)的排序快
递归版本
public static void mergeSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
public static void process(int[] arr, int L, i
if (L == R) { // base case
return;
}
int mid = L + ((R - L) >> 1);
process(arr, L, mid); // 左边排好序
process(arr, mid + 1, R); // 右边排好序
merge(arr, L, mid, R); // 最后merge一下
}
public static void merge(int[] arr, int L, int
int[] help = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
while (p1 <= M && p2 <= R) {
help[i++] = arr[p1] <= arr[p2] ? arr[p++] : arr[p2++];
}
while (p1 <= M) { // 边界处理,防止右边拷贝完了,左边还没
help[i++] = arr[p1++];
}
while (p2 <= R) { // 边界处理,防止左边拷贝完了,右边还没
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
}
非递归版本
public static void mergeSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int N = arr.length;
// 步长
int mergeSize = 1;
while (mergeSize < N) { // log N
// 当前左组的,第一个位置
int L = 0;
while (L < N) {
if (mergeSize >= N - L) { // 边界,最后一个,没有右组就停下
break;
}
int M = L + mergeSize - 1;
int R = M + Math.min(mergeSize, N - M - 1);
merge(arr, L, M, R);
L = R + 1;
}
// 防止溢出,因为mergeSize会放大两倍而超过N的值
if (mergeSize > N / 2) {
break;
}
// 每次扩大两倍步长进行merge,比如第一次merge的两组分别为一个,第二次merge的两组就是分别两个
mergeSize <<= 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
- 思路: 转化成求数组中的每个数,右边比它大的数有几个。结果就是,每个数 * 这个数右边比它大的数的个数的结果,全部加起来。归并排序的思路是,求每个数在2个数中、4个数中、8个数中。。。右边比它大的数的个数。
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
public static int process(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
// l < r
int mid = l + ((r - l) >> 1);
return
process(arr, l, mid) // 左组最小和
+
process(arr, mid + 1, r) // 右组最小和
+
merge(arr, l, mid, r); // 左右组排序,并计算总最小和
}
public static int merge(int[] arr, int L, int m, int r) {
int[] help = new int[r - L + 1];
int i = 0;
int p1 = L;
int p2 = m + 1;
int res = 0;
while (p1 <= m && p2 <= r) {
// 每次比较时,收集右组有几个数,比当前左组的那个数大
res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
// 不能是小于等于,等于时一定要先拷贝右边的
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return res;
}
题目二
在一个数组中,任何一个前面的数a,和任何一个后面的数b,如果(a,b)是降序的,就称为逆序对返回数组中所有的逆序对。 思路: 也就是求,每个数右边有几个数比它小,然后加起来。
public static int reverPairNumber(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
// arr[L..R]既要排好序,也要求逆序对数量返回
// 所有merge时,产生的逆序对数量,累加,返回
// 左 排序 merge并产生逆序对数量
// 右 排序 merge并产生逆序对数量
public static int process(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
// l < r
int mid = l + ((r - l) >> 1);
return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r);
}
// 之所以倒着merge,是因为本题是求右边小的数的个数,和上一题相反
public static int merge(int[] arr, int L, int m, int r) {
int[] help = new int[r - L + 1];
int i = help.length - 1;
int p1 = m;
int p2 = r;
int res = 0;
while (p1 >= L && p2 > m) {
res += arr[p1] > arr[p2] ? (p2 - m) : 0;
help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--];
}
while (p1 >= L) {
help[i--] = arr[p1--];
}
while (p2 > m) {
help[i--] = arr[p2--];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return res;
}
题目三
- 在一个数组中,对于每个数num,求有多少个后面的数 * 2 依然<num,求总个数
- 比如:[3,1,7,0,2]
- 3的后面有:1,0
- 1的后面有:0
- 7的后面有:0,2
- 0的后面没有
- 2的后面没有
- 所以总共有5个
- leetcode.cn/problems/re…
public static int reversePairs(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
public static int process(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
// l < r
int mid = l + ((r - l) >> 1);
return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r);
}
public static int merge(int[] arr, int L, int m, int r) {
// [L....M] [M+1....R]
int ans = 0;
int windowR = m + 1;
for (int i = L; i <= m; i++) {
while (windowR <= r && (long) arr[i] > (long) arr[windowR] * 2) {
windowR++;
}
ans += windowR - m - 1;
}
int[] help = new int[r - L + 1];
int i = 0;
int p1 = L;
int p2 = m + 1;
while (p1 <= m && p2 <= r) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return ans;
}