题目一、归并排序
1)整体就是一个简单递归,左边排好序、右边排好序、让其整体有序
2)让其整体有序的过程里用了外排序方法
3)利用master公式来求解时间复杂度
4)归并排序的实质
时间复杂度O(N logN),额外空间复杂度O(N)
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
public static void process(int[] arr, int L, int R) {
if (L == R) {
return;
}
int mid = L + ((R - L) >> 1);
process(arr, L, mid);
process(arr, mid + 1, R);
merge(arr, L, mid, R);
}
public static void 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;
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];
}
}
外排序:两个指针,比较后的结果,拷贝到一个外部的数组中,再拷贝回原数组中。
leftMax, P(0, 4)
leftMax, P(0, 2)
leftMax, P(0, 1)
leftMax, P(0, 0)
-----返回当前值:9
rightMax, P(1, 1)
-----返回当前值:8
-----开始merge:[0]-[1]
9 8 7 6 5 4 3 2 1
help数组是:
8 9
rightMax, P(2, 2)
-----返回当前值:7
-----开始merge:[0]-[2]
8 9 7 6 5 4 3 2 1
help数组是:
7 8 9
rightMax, P(3, 4)
leftMax, P(3, 3)
-----返回当前值:6
rightMax, P(4, 4)
-----返回当前值:5
-----开始merge:[3]-[4]
7 8 9 6 5 4 3 2 1
help数组是:
5 6
-----开始merge:[0]-[4]
7 8 9 5 6 4 3 2 1
help数组是:
5 6 7 8 9
rightMax, P(5, 8)
leftMax, P(5, 6)
leftMax, P(5, 5)
-----返回当前值:4
rightMax, P(6, 6)
-----返回当前值:3
-----开始merge:[5]-[6]
5 6 7 8 9 4 3 2 1
help数组是:
3 4
rightMax, P(7, 8)
leftMax, P(7, 7)
-----返回当前值:2
rightMax, P(8, 8)
-----返回当前值:1
-----开始merge:[7]-[8]
5 6 7 8 9 3 4 2 1
help数组是:
1 2
-----开始merge:[5]-[8]
5 6 7 8 9 3 4 1 2
help数组是:
1 2 3 4
-----开始merge:[0]-[8]
5 6 7 8 9 1 2 3 4
help数组是:
1 2 3 4 5 6 7 8 9
O(N2):浪费了大量的比较行为。每一圈仅比较得出一个结果。
O(NlogN):没有浪费比较。merge了比较结果,放入数组中,下一次更大范围的比较基于了上一次的merge结果。
题目二、归并排序的扩展
小和问题和逆序对问题
- 小和问题
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
[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右边比1大的数:4个1
3右边比3大的数:2个3
4右边比4大的数:1个4
2右边比2大的数:1个2
5右边比5大的数:0个5
4 * 1 + 2 * 3 + 1 * 4 + 1 * 2 = 16
[1,3,4,2,5]
以1举例
1和3组成[1,3],找到3比1大
[1,3]和4组成[1,3,4],找到4比1大
[1,3,4]和[2,5]组成[1,2,3,4,5],找到2和5比1大,注意因为2比1大,所以直接确定了有2个数比1大,而不用1分别和2、5比较。
是要排序的!
这个merge和上面的那个经典merge有一点不一样
就是当左右指针的数字大小相同时,移动的是右边的指针。
因为要计算右边的大数字的个数
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
// arr[L..R]既要排好序,也要求小和
public static int process(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
int mid = l + ((r - 1) >> 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;
}
题目六、荷兰国旗问题
问题一
给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)
问题二(荷兰国旗问题)
给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)
总结:
当[i]<=num,[i]和‘<=区的下一个数’作交换,’<=区’外扩1
当[i]>num,i++
直到i++越界
当[i]<num,[i]和‘小于区间的下一个’交换,小于区右扩1,i++
当[i]=num,i++
当[i]>num,[i]和’大于区间的前一个’交换,大于区左扩1,i不动
直到当i遇见大于区
快排1.0版本
num是数组中最后一个数
排好后,num与大于区的第一个数作交换,且这个位置就是num的最终位置
然后两个区域分别重复以上行为
快排2.0版本
num与大于区第一个数交换,num的位置确定
快排1.0和2.0的时间复杂度都是O(N2)
最坏情况:12345(6)
最好情况:num是中间值
快排3.0版本
随机选一个数,交换,放在最右端,作为num
因为是概率事件,经过复杂的证明过程,时间复杂度O(NlogN)
快排的空间复杂度O(N)
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
// arr[l..r]排好序
public static void quickSort(int[] arr, int L, int R) {
if (L < R) {
swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
int[] p = partition(arr, L, R);
quickSort(arr, L, p[0] - 1);// <区
quickSort(arr, p[1] + 1, R);// >区
}
}
// 这是一个处理arr[l..r】的函数
// 默认以arr[r]做划分,arr[r]->p <p ==p >p
// 返回等于区域(左边界,右边界),所以返回一个长度为2的数组res,res[0] res[1]
public static int[] partition(int[] arr, int L, int R) {
int less = L - 1;// <区右边界
int more = R;// >区左边界
while (L < more) {// L表示当前数的位置 arr[R] -> 划分值
if (arr[L] < arr[R]) { // 当前数< 划分值
swap(arr, ++less, L++);
} else if (arr[L] > arr[R]) { // 当前数 >划分值
swap(arr, --more, L);
} else {
L++;
}
}
swap(arr, more, R);
return new int[]{less + 1, more};
}
partition
一定返回一个长度为2的数组,用于描述’=num区’的左右边界