归并排序补充题目(难)
题目描述:
https://leetcode.com/problems/count-of-range-sum/
给定一个数组arr,两个整数lower和upper,
返回arr中有多少个子数组的累加和在[lower,upper]范围上
public static int countRangeSum(int[] nums, int lower, int upper) {
if (nums == null || nums.length == 0) {
return 0;
}
long[] sum = new long[nums.length];
sum[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
sum[i] = sum[i - 1] + nums[i];
}
return process(sum, 0, sum.length - 1, lower, upper);
}
private static int process(long[] sum, int l, int r, int lower, int upper) {
if (l == r) {
return sum[l] >= lower && sum[l] <= upper ? 1 : 0;
}
int m = l + ((r - l) >> 1);
int left = process(sum, l, m, lower, upper);
int right = process(sum, m + 1, r, lower, upper);
return left + right + merge(sum, l, m, r, lower, upper);
}
public static int merge(long[] sum, int l, int m, int r, int lower, int upper) {
int ans = 0;
int windowl = l;
int windowr = l;
for (int i = m + 1; i <= r; i++) {
long max = sum[i] - lower;
long min = sum[i] - upper;
while (windowr <= m && sum[windowr] <= max) {
windowr++;
}
while (windowl <= m && sum[windowl] < min) {
windowl++;
}
ans += windowr - windowl;
}
int mid = m + 1;
int left = l;
long[] help = new long[r - l + 1];
int i = 0;
while (left <= m && mid <= r) {
help[i++] = sum[left] <= sum[mid] ? sum[left++] : sum[mid++];
}
while (left <= m) {
help[i++] = sum[left++];
}
while (mid <= r) {
help[i++] = sum[mid++];
}
for (int j = 0; j < help.length; j++) {
sum[l++] = help[j];
}
return ans;
}
快速排序
Partition过程
给定一个数组arr,和一个整数num。请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)
荷兰国旗问题
给定一个数组arr,和一个整数num。请把小于num的数放在数组的左边,等于num的数放在中间,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)
快速排序1.0
在arr[L…R]范围上,进行快速排序的过程:
1)用arr[R]对该范围做partition,<= arr[R]的数在左部分并且保证arr[R]最后来到左部分的最后一个位置,记为M; <= arr[R]的数在右部分(arr[M+1…R])
2)对arr[L…M-1]进行快速排序(递归)
3)对arr[M+1…R]进行快速排序(递归)
因为每一次partition都会搞定一个数的位置且不会再变动,所以排序能完成
快速排序2.0
在arr[L…R]范围上,进行快速排序的过程:
1)用arr[R]对该范围做partition,< arr[R]的数在左部分,== arr[R]的数中间,>arr[R]的数在右部分。假设== arr[R]的数所在范围是[a,b]
2)对arr[L…a-1]进行快速排序(递归)
3)对arr[b+1…R]进行快速排序(递归)
因为每一次partition都会搞定一批数的位置且不会再变动,所以排序能完成
快速排序1.0和2.0的时间复杂度分析
数组已经有序的时候就是复杂度最高的时候
时间复杂度O(N^2)
快速排序3.0(随机快排+荷兰国旗技巧优化)
在arr[L…R]范围上,进行快速排序的过程:
1)在这个范围上,随机选一个数记为num,
1)用num对该范围做partition,< num的数在左部分,== num的数中间,>num的数在右部分。假设== num的数所在范围是[a,b]
2)对arr[L…a-1]进行快速排序(递归)
3)对arr[b+1…R]进行快速排序(递归)
因为每一次partition都会搞定一批数的位置且不会再变动,所以排序能完成
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
int[] arr3 = copyArray(arr1);
quickSort1(arr1);
quickSort2(arr2);
quickSort3(arr3);
if (!isEqual(arr1, arr2) || !isEqual(arr2, arr3)) {
printArray(arr1);
printArray(arr2);
printArray(arr3);
System.out.println(" arr1 arr2 " + isEqual(arr1, arr2));
System.out.println(" arr2 arr3 " + isEqual(arr3, arr2));
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Oops!");
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
private static void quickSort3(int[] arr3) {
if (arr3 == null || arr3.length < 2) {
return;
}
process3(arr3, 0, arr3.length - 1);
}
private static void process3(int[] arr3, int l, int r) {
if (l >= r) {
return;
}
swap(arr3, l + (int) (Math.random() * (r - l + 1)), r);
int[] equalarea = netherlandsFlag(arr3, l, r);
process3(arr3, l, equalarea[0] - 1);
process3(arr3, equalarea[1] + 1, r);
}
public static void quickSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process2(arr, 0, arr.length - 1);
}
private static void process2(int[] arr, int l, int r) {
if (l >= r) {
return;
}
int[] equal = netherlandsFlag(arr, l, r);
process2(arr, l, equal[0] - 1);
process2(arr, equal[1] + 1, r);
}
public static int[] netherlandsFlag(int[] arr, int l, int r) {
if (l > r) {
return new int[]{-1, -1};
}
if (l == r) {
return new int[]{l, l};
}
int lessequal = l - 1;
int index = l;
int morequal = r;
while (index < morequal) {
if (arr[index] < arr[r]) {
swap(arr, index++, ++lessequal);
} else if (arr[index] > arr[r]) {
swap(arr, index, --morequal);
} else if (arr[index] == arr[r]) {
index++;
}
}
swap(arr, morequal, r);
return new int[]{lessequal + 1, morequal};
}
private static void quickSort1(int[] arr1) {
if (arr1 == null || arr1.length < 2) {
return;
}
process1(arr1, 0, arr1.length - 1);
}
private static void process1(int[] arr1, int l, int r) {
if (l >= r) {
return;
}
int m = partition(arr1, l, r);
process1(arr1, l, m - 1);
process1(arr1, m + 1, r);
}
public static int partition(int[] arr, int l, int r) {
if (l > r) {
return -1;
}
if (l == r) {
return l;
}
int lessequal = l - 1;
int index = l;
while (index < r) {
if (arr[index] <= arr[r]) {
swap(arr, index, ++lessequal);
}
index++;
}
swap(arr, ++lessequal, r);
return lessequal;
}
随机快排的时间复杂度分析
1)通过分析知道,划分值越靠近中间,性能越好;越靠近两边,性能越差
2)随机选一个数进行划分的目的就是让好情况和差情况都变成概率事件
3)把每一种情况都列出来,会有每种情况下的时间复杂度,但概率都是1/N
4)那么所有情况都考虑,时间复杂度就是这种概率模型下的长期期望!
时间复杂度O(N*logN),额外空间复杂度O(logN)都是这么来的。
随机快排的实现
要求掌握递归和非递归两种方式写出随机快排
private static void quickSort3(int[] arr3) {
if (arr3 == null || arr3.length < 2) {
return;
}
process3(arr3, 0, arr3.length - 1);
}
private static void process3(int[] arr3, int l, int r) {
if (l >= r) {
return;
}
swap(arr3, l + ((int) Math.random() * (r - l + 1)), r);
int[] equalarea = netherlandsFlag(arr3, l, r);
process3(arr3, l, equalarea[0] - 1);
process3(arr3, equalarea[1] + 1, r);
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static int[] netherlandsFlag(int[] arr, int l, int r) {
if (l > r) {
return new int[]{-1, -1};
}
if (l == r) {
return new int[]{l, l};
}
int less = l - 1;
int index = l;
int more = r;
while (index < more) {
if (arr[index] == arr[r]) {
index++;
} else if (arr[index] > arr[r]) {
swap(arr, index++, ++less);
} else if (arr[index] < arr[r]) {
swap(arr, index, --more);
}
}
swap(arr, more, r);
return new int[]{less + 1, more};
}
// 快排非递归版本需要的辅助类
// 要处理的是什么范围上的排序
public static class Op {
public int l;
public int r;
public Op(int left, int right) {
l = left;
r = right;
}
}
// 快排3.0 非递归版本 用栈来执行
private static void quickSort1(int[] arr1) {
if (arr1 == null || arr1.length < 2) {
return;
}
int n = arr1.length;
swap(arr1, (int) (Math.random() * n), n - 1);
int[] equalarea = netherlandsFlag(arr1, 0, n - 1);
Stack<Op> stack = new Stack();
stack.push(new Op(0, equalarea[0] - 1));
stack.push(new Op(equalarea[1] + 1, n - 1));
while (!stack.isEmpty()) {
Op pop = stack.pop();
if (pop.l < pop.r) {
swap(arr1, pop.l + ((int) Math.random() * (pop.r - pop.l + 1)), pop.r);
equalarea = netherlandsFlag(arr1, pop.l, pop.r);
stack.push(new Op(pop.l, equalarea[0] - 1));
stack.push(new Op(equalarea[1] + 1, pop.r));
}
}
}
// 快排3.0 非递归版本 用队列来执行
public static void quickSort2(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
swap(arr, ((int) Math.random() * n), n - 1);
Queue<Op> queue = new LinkedList();
int[] equalarea = netherlandsFlag(arr, 0, n - 1);
queue.offer(new Op(0, equalarea[0] - 1));
queue.offer(new Op(equalarea[1] + 1, n - 1));
while (!queue.isEmpty()) {
Op poll = queue.poll();
if (poll.l < poll.r) {
swap(arr, poll.l + ((int) Math.random() * (poll.r - poll.l + 1)), poll.r);
equalarea = netherlandsFlag(arr, poll.l, poll.r);
queue.offer(new Op(poll.l, equalarea[0] - 1));
queue.offer(new Op(equalarea[1] + 1, poll.r));
}
}
}
// 生成随机数组(用于测试)
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// 拷贝数组(用于测试)
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// 对比两个数组(用于测试)
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// 打印数组(用于测试)
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// 跑大样本随机测试(对数器)
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
System.out.println("test begin");
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
int[] arr3 = copyArray(arr1);
quickSort1(arr1);
quickSort2(arr2);
quickSort3(arr3);
if (!isEqual(arr1, arr2) || !isEqual(arr1, arr3)) {
succeed = false;
break;
}
}
System.out.println("test end");
System.out.println("测试" + testTime + "组是否全部通过:" + (succeed ? "是" : "否"));
}
// 双向链表的随机快速排序
// 课上没有讲,因为这是群里同学问的问题
// 作为补充放在这,有需要的同学可以看看
// 和课上讲的数组的经典快速排序在算法上没有区别
// 但是coding需要更小心
public class Code04_DoubleLinkedListQuickSort {
public static class Node {
public int value;
public Node last;
public Node next;
public Node(int v) {
value = v;
}
}
public static Node quickSort(Node h) {
if (h == null) {
return null;
}
int N = 0;
Node c = h;
Node e = null;
while (c != null) {
N++;
e = c;
c = c.next;
}
return process(h, e, N).h;
}
public static class HeadTail {
public Node h;
public Node t;
public HeadTail(Node head, Node tail) {
h = head;
t = tail;
}
}
// L...R是一个双向链表的头和尾,
// L的last指针指向null,R的next指针指向null
// 也就是说L的左边没有,R的右边也没节点
// 就是一个正常的双向链表,一共有N个节点
// 将这一段用随机快排的方式排好序
// 返回排好序之后的双向链表的头和尾(HeadTail)
public static HeadTail process(Node L, Node R, int N) {
if (L == null) {
return null;
}
if (L == R) {
return new HeadTail(L, R);
}
// L..R上不只一个节点
// 随机得到一个随机下标
int randomIndex = (int) (Math.random() * N);
// 根据随机下标得到随机节点
Node randomNode = L;
while (randomIndex-- != 0) {
randomNode = randomNode.next;
}
// 把随机节点从原来的环境里分离出来
// 比如 a(L) -> b -> c -> d(R), 如果randomNode = c,那么调整之后
// a(L) -> b -> d(R), c会被挖出来,randomNode = c
if (randomNode == L || randomNode == R) {
if (randomNode == L) {
L = randomNode.next;
L.last = null;
} else {
randomNode.last.next = null;
}
} else { // randomNode一定是中间的节点
randomNode.last.next = randomNode.next;
randomNode.next.last = randomNode.last;
}
randomNode.last = null;
randomNode.next = null;
Info info = partition(L, randomNode);
// <randomNode的部分去排序
HeadTail lht = process(info.lh, info.lt, info.ls);
// >randomNode的部分去排序
HeadTail rht = process(info.rh, info.rt, info.rs);
// 左部分排好序、右部分排好序
// 把它们串在一起
if (lht != null) {
lht.t.next = info.eh;
info.eh.last = lht.t;
}
if (rht != null) {
info.et.next = rht.h;
rht.h.last = info.et;
}
// 返回排好序之后总的头和总的尾
Node h = lht != null ? lht.h : info.eh;
Node t = rht != null ? rht.t : info.et;
return new HeadTail(h, t);
}
public static class Info {
public Node lh;
public Node lt;
public int ls;
public Node rh;
public Node rt;
public int rs;
public Node eh;
public Node et;
public Info(Node lH, Node lT, int lS, Node rH, Node rT, int rS, Node eH, Node eT) {
lh = lH;
lt = lT;
ls = lS;
rh = rH;
rt = rT;
rs = rS;
eh = eH;
et = eT;
}
}
// (L....一直到空),是一个双向链表
// pivot是一个不在(L....一直到空)的独立节点,它作为划分值
// 根据荷兰国旗问题的划分方式,把(L....一直到空)划分成:
// <pivot 、 =pivot 、 >pivot 三个部分,然后把pivot融进=pivot的部分
// 比如 4(L)->6->7->1->5->0->9->null pivot=5(这个5和链表中的5,是不同的节点)
// 调整完成后:
// 4->1->0 小于的部分
// 5->5 等于的部分
// 6->7->9 大于的部分
// 三个部分是断开的
// 然后返回Info:
// 小于部分的头、尾、节点个数 : lh,lt,ls
// 大于部分的头、尾、节点个数 : rh,rt,rs
// 等于部分的头、尾 : eh,et
public static Info partition(Node L, Node pivot) {
Node lh = null;
Node lt = null;
int ls = 0;
Node rh = null;
Node rt = null;
int rs = 0;
Node eh = pivot;
Node et = pivot;
Node tmp = null;
while (L != null) {
tmp = L.next;
L.next = null;
L.last = null;
if (L.value < pivot.value) {
ls++;
if (lh == null) {
lh = L;
lt = L;
} else {
lt.next = L;
L.last = lt;
lt = L;
}
} else if (L.value > pivot.value) {
rs++;
if (rh == null) {
rh = L;
rt = L;
} else {
rt.next = L;
L.last = rt;
rt = L;
}
} else {
et.next = L;
L.last = et;
et = L;
}
L = tmp;
}
return new Info(lh, lt, ls, rh, rt, rs, eh, et);
}
// 为了测试
public static class NodeComp implements Comparator<Node> {
@Override
public int compare(Node o1, Node o2) {
return o1.value - o2.value;
}
}
// 为了测试
public static Node sort(Node head) {
if (head == null) {
return null;
}
ArrayList<Node> arr = new ArrayList<>();
while (head != null) {
arr.add(head);
head = head.next;
}
arr.sort(new NodeComp());
Node h = arr.get(0);
h.last = null;
Node p = h;
for (int i = 1; i < arr.size(); i++) {
Node c = arr.get(i);
p.next = c;
c.last = p;
c.next = null;
p = c;
}
return h;
}
// 为了测试
public static Node generateRandomDoubleLinkedList(int n, int v) {
if (n == 0) {
return null;
}
Node[] arr = new Node[n];
for (int i = 0; i < n; i++) {
arr[i] = new Node((int) (Math.random() * v));
}
Node head = arr[0];
Node pre = head;
for (int i = 1; i < n; i++) {
pre.next = arr[i];
arr[i].last = pre;
pre = arr[i];
}
return head;
}
// 为了测试
public static Node cloneDoubleLinkedList(Node head) {
if (head == null) {
return null;
}
Node h = new Node(head.value);
Node p = h;
head = head.next;
while (head != null) {
Node c = new Node(head.value);
p.next = c;
c.last = p;
p = c;
head = head.next;
}
return h;
}
// 为了测试
public static boolean equal(Node h1, Node h2) {
return doubleLinkedListToString(h1).equals(doubleLinkedListToString(h2));
}
// 为了测试
public static String doubleLinkedListToString(Node head) {
Node cur = head;
Node end = null;
StringBuilder builder = new StringBuilder();
while (cur != null) {
builder.append(cur.value + " ");
end = cur;
cur = cur.next;
}
builder.append("| ");
while (end != null) {
builder.append(end.value + " ");
end = end.last;
}
return builder.toString();
}
// 为了测试
public static void main(String[] args) {
int N = 500;
int V = 500;
int testTime = 10000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int size = (int) (Math.random() * N);
Node head1 = generateRandomDoubleLinkedList(size, V);
Node head2 = cloneDoubleLinkedList(head1);
Node sort1 = quickSort(head1);
Node sort2 = sort(head2);
if (!equal(sort1, sort2)) {
System.out.println("出错了!");
break;
}
}
System.out.println("测试结束");
}
}