1. 堆(优先级队列)

(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象

完全二叉树:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。需要注意的是,满二叉树肯定是完全二叉树,而完全二叉树不一定是满二叉树。

每一棵子树根结点最大的堆(即根节点大于等于子节点)叫做最大堆或大根堆根结点最小的堆叫做最小堆或小根堆

2. 优先级队列(PriorityQueue)与比较器

  1. public static class MyComparator implements Comparator<Integer> {
  2. // 负数,第一个参数在前
  3. // 正数,第二个参数在前
  4. // 0,不作交换
  5. @Override
  6. public int compare(Integer o1, Integer o2) {
  7. if (o1 > o2) {
  8. return -1;
  9. } else if (o2 > o1) {
  10. return 1;
  11. } else {
  12. return 0;
  13. }
  14. }
  15. }
  16. public static void main(String[] args) {
  17. PriorityQueue<Integer> heap = new PriorityQueue<>(); // 默认小根堆的优先队列
  18. heap.add(5);
  19. heap.add(3);
  20. heap.add(8);
  21. heap.add(1);
  22. System.out.println(heap.peek()); // 查看队头
  23. for (Integer integer : heap) {
  24. System.out.print(integer + " ");
  25. }
  26. System.out.println();
  27. // PriorityQueue<Integer> heap2 = new PriorityQueue<>(new MyComparator()); // 大根堆的优先队列 构造时传入比较器
  28. PriorityQueue<Integer> heap2 = new PriorityQueue<>((o1,o2)-> o2-o1); // 大根堆的优先队列 构造时传入比较器
  29. heap2.add(5);
  30. heap2.add(3);
  31. heap2.add(8);
  32. heap2.add(1);
  33. System.out.println(heap2.peek()); // 查看队头
  34. for (Integer integer : heap2) {
  35. System.out.print(integer + " ");
  36. }
  37. System.out.println();
  38. }

3. 数组实现大根堆

  1. public static class MyMaxheap {
  2. private int[] heap;
  3. private final int limit; // heap的最大值
  4. private int size; // heap当前大小
  5. public MyMaxheap(int limit) {
  6. this.limit = limit;
  7. heap = new int[limit];
  8. size = 0;
  9. }
  10. public boolean isEmpty() {
  11. return size == 0;
  12. }
  13. public boolean isFull() {
  14. return size == limit;
  15. }
  16. public void push(int value) {
  17. if (size == limit) {
  18. throw new RuntimeException("当前堆已满!");
  19. }
  20. heap[size] = value;
  21. heapInsert(heap, size++);
  22. }
  23. public int pop() {
  24. int ans = heap[0];
  25. swap(heap, 0, --size);
  26. heapify(heap, 0, size);
  27. return ans;
  28. }
  29. // 向下沉
  30. private void heapify(int[] heap, int index, int size) {
  31. int left = index * 2 + 1;
  32. while (left < size) { // 存在左孩子时 有可能存在右孩子
  33. int largest = left + 1 < size && heap[left + 1] > heap[left] ? left + 1 : left; // 判断是否有右孩子 并且挑出最大值的下标
  34. largest = heap[largest] > heap[index] ? largest : index; // 如果孩子大于当前push节点 则赋值下标 否则下标为index
  35. if (largest == index) { // 最大值是自身 无需下沉
  36. break;
  37. }
  38. swap(heap, largest, index); // 存在孩子大于当前push节点 交换并下沉(使最大值孩子成为根节点,push节点成为孩子)
  39. index = largest; // 继续与下面孩子进行比较
  40. left = index * 2 + 1; // 是否存在左孩子 由while循环决定
  41. }
  42. }
  43. private void swap(int[] heap, int i, int j) {
  44. int temp = heap[i];
  45. heap[i] = heap[j];
  46. heap[j] = temp;
  47. }
  48. // 向上移动
  49. private void heapInsert(int[] heap, int index) {
  50. while (heap[index] > heap[(index - 1) / 2]) { // 如果当前push的节点比它的根节点大则
  51. swap(heap, index, (index - 1) / 2); // 进行交换
  52. index = (index - 1) / 2;
  53. }
  54. }
  55. }
  56. public static class RightMaxHeap {
  57. private int[] arr;
  58. private final int limit;
  59. private int size;
  60. public RightMaxHeap(int limit) {
  61. arr = new int[limit];
  62. this.limit = limit;
  63. size = 0;
  64. }
  65. public boolean isEmpty() {
  66. return size == 0;
  67. }
  68. public boolean isFull() {
  69. return size == limit;
  70. }
  71. public void push(int value) {
  72. if (size == limit) {
  73. throw new RuntimeException("heap is full");
  74. }
  75. arr[size++] = value;
  76. }
  77. public int pop() {
  78. int maxIndex = 0;
  79. for (int i = 1; i < size; i++) {
  80. if (arr[i] > arr[maxIndex]) {
  81. maxIndex = i;
  82. }
  83. }
  84. int ans = arr[maxIndex];
  85. arr[maxIndex] = arr[--size];
  86. return ans;
  87. }
  88. }
  89. public static class MyComparator implements Comparator<Integer> {
  90. @Override
  91. public int compare(Integer o1, Integer o2) {
  92. return o2 - o1;
  93. }
  94. }
  95. public static void main(String[] args) {
  96. // 小根堆
  97. PriorityQueue<Integer> heap = new PriorityQueue<>(new MyComparator());
  98. heap.add(5);
  99. heap.add(5);
  100. heap.add(5); //堆是可以添加重复元素的
  101. heap.add(3);
  102. // 5 , 3
  103. System.out.println(heap.peek());
  104. heap.add(7);
  105. heap.add(0);
  106. heap.add(7);
  107. heap.add(0);
  108. heap.add(7);
  109. heap.add(0);
  110. System.out.println(heap.peek());
  111. while (!heap.isEmpty()) {
  112. System.out.println(heap.poll());
  113. }
  114. int value = 1000;
  115. int limit = 100;
  116. int testTimes = 1000000;
  117. for (int i = 0; i < testTimes; i++) {
  118. int curLimit = (int) (Math.random() * limit) + 1;
  119. MyMaxheap my = new MyMaxheap(curLimit);
  120. RightMaxHeap test = new RightMaxHeap(curLimit);
  121. int curOpTimes = (int) (Math.random() * limit);
  122. for (int j = 0; j < curOpTimes; j++) {
  123. if (my.isEmpty() != test.isEmpty()) {
  124. System.out.println("Oops!");
  125. }
  126. if (my.isFull() != test.isFull()) {
  127. System.out.println("Oops!");
  128. }
  129. if (my.isEmpty()) {
  130. int curValue = (int) (Math.random() * value);
  131. my.push(curValue);
  132. test.push(curValue);
  133. } else if (my.isFull()) {
  134. if (my.pop() != test.pop()) {
  135. System.out.println("Oops!");
  136. }
  137. } else {
  138. if (Math.random() < 0.5) {
  139. int curValue = (int) (Math.random() * value);
  140. my.push(curValue);
  141. test.push(curValue);
  142. } else {
  143. if (my.pop() != test.pop()) {
  144. System.out.println("Oops!");
  145. }
  146. }
  147. }
  148. }
  149. }
  150. System.out.println("finish!");
  151. }

4. 堆排序

  1. 0~N 范围上变成大根堆,heapsize = 需排序数组长度
  2. 0 与 N 进行交换值
  3. 0~N-1 范围上变成大根堆,再进行 0与 N-1 交换,直到heapsize— 成为1(只剩下0~0没进行heapInster)

时间复杂程度:O(N*logN)

不断的缩小要排序数组的大小 每次将堆顶(大根堆即最大值)放置到末尾 并断开连接(下次排序此元素不参与)

4.1. 自底向上变成大根堆

在上面堆排序我们使用的从根节点到底部叶子节点 headpSort 时间复杂程度为:O(N*logN)

而我们从底部headpSort时 叶子节点只需根自己比较即是大根堆 时间复杂程度为:O(N) 因为大量的节点存在于叶子节点N个数就要操作N*logN次 而自底向上只需要 N次操作

注意:自底向上建堆必须给定一个具体数组(必须知晓叶子节点),无法像自顶向下建堆灵活一个一个建立

  1. public static void heapSort(int[] arr) {
  2. if (arr == null || arr.length < 2) {
  3. return;
  4. }
  5. // 自顶向下插入堆 O(N*logN)
  6. // for (int i = 0; i < arr.length; i++) {
  7. // heapInster(arr,i);
  8. // }
  9. // 自底向上推进堆 O(N)
  10. for (int i = arr.length - 1; i >= 0; i--) { // 整个堆的最后的叶子节点此操作 无需下沉
  11. heapify(arr, i, arr.length);
  12. }
  13. int heapsize = arr.length;
  14. swap(arr, 0, --heapsize); // heapInster后第一个为大根堆最大值 直接丢到数组末端并heapsize-- 下次操作不包括此元素
  15. while (heapsize > 0) {
  16. heapify(arr, 0, heapsize);// 向下沉
  17. swap(arr, 0, --heapsize); // 交换
  18. }
  19. }
  20. // 向下移动
  21. private static void heapify(int[] arr, int i, int heapsize) {
  22. int left = i * 2 + 1;
  23. while (left < heapsize) {
  24. int largest = left + 1 < heapsize && arr[left + 1] > arr[left] ? left + 1 : left;
  25. largest = arr[i] < arr[largest] ? largest : i;
  26. if (largest == i) {
  27. break;
  28. }
  29. swap(arr, i, largest);
  30. i = largest;
  31. left = i * 2 + 1;
  32. }
  33. }
  34. // 向上移动
  35. private static void heapInster(int[] arr, int i) {
  36. while (arr[i] > arr[(i - 1) / 2]) {
  37. swap(arr, i, (i - 1) / 2);
  38. i = (i - 1) / 2;
  39. }
  40. }
  41. private static void swap(int[] arr, int i, int j) {
  42. int temp = arr[i];
  43. arr[i] = arr[j];
  44. arr[j] = temp;
  45. }

线段最大重合问题

  1. 给定很多线段,每个线段都有两个数[start, end],
  2. 表示线段开始位置和结束位置,左右都是闭区间
  3. 规定:
  4. 1)线段的开始和结束位置一定都是整数值
  5. 2)线段重合区域的长度必须>=1
  6. 返回线段最多重合区域中,包含了几条线段
  1. //暴力解法
  2. public static int maxCover1(int[][] lines) {
  3. int min = Integer.MAX_VALUE;
  4. int max = Integer.MIN_VALUE;
  5. for (int i = 0; i < lines.length; i++) { //找出线段最小值和最大值
  6. min = Math.min(min, lines[i][0]);
  7. max = Math.max(max, lines[i][1]);
  8. }
  9. int cover = 0;
  10. for (double p = min + 0.5; p < max; p += 1) { //每个隔0.5
  11. int cur = 0;
  12. for (int i = 0; i < lines.length; i++) {
  13. if (lines[i][0] < p && lines[i][1] > p) { //数有多少条线处于这0.5中 即有多少条重合线段
  14. cur++;
  15. }
  16. }
  17. cover = Math.max(cover, cur); //返回最大重合的数
  18. }
  19. return cover;
  20. }
  21. //堆解法
  22. public static int maxCovcer2(int[][] lines) {
  23. //比如, m = { {5,7}, {1,4}, {2,6} } 跑完如下的code之后变成:{ {1,4}, {2,6}, {5,7} }
  24. Arrays.sort(lines,(o1,o2) -> (o1[0] - o2[0])); //以线段开始点 排序
  25. //准备小根堆
  26. PriorityQueue<Integer> heap = new PriorityQueue<Integer>();
  27. int max = 0;
  28. for (int[] line : lines) {
  29. //堆不为空 并且当前线段开头 大于等于 堆顶
  30. while(!heap.isEmpty() && heap.peek() <= line[0]) {
  31. heap.poll(); //删除堆顶
  32. }
  33. heap.add(line[1]); //添加线段结束点到堆中
  34. max = Math.max(max, heap.size());
  35. }
  36. return max;
  37. }

5. 加强堆

  1. 系统提供的堆只能删除头节点(pop) 无法快速删除其他位置的节点
  2. 加强堆建立一个反向索引 通过值查找堆中索引 可以更快删除 删除之后与最后一个节点进行交换 再进行上移和下沉 保存堆的完整结构

需要提供一个比较器,来定义大根堆还是小根堆

  1. public class HeapGreater<T> {
  2. private ArrayList<T> heap;
  3. private HashMap<T, Integer> indexMap; // 反向索引表
  4. private int heapSize;
  5. private Comparator<? super T> comp;
  6. public HeapGreater(Comparator<T> comp) {
  7. heap = new ArrayList<>();
  8. indexMap = new HashMap<>();
  9. heapSize = 0;
  10. this.comp = comp;
  11. }
  12. public boolean isEmpty() {
  13. return heapSize == 0;
  14. }
  15. public int size() {
  16. return heapSize;
  17. }
  18. public boolean contains(T obj) { // 返回此元素的索引是否存在
  19. return indexMap.containsKey(obj);
  20. }
  21. public T peek() {
  22. return heap.get(0); // 获取堆顶
  23. }
  24. public void push(T obj) { // 往堆添加元素
  25. heap.add(obj);
  26. indexMap.put(obj, heapSize); // 反向索引表
  27. heapInsert(heapSize++);
  28. }
  29. public void heapInsert(int index) {
  30. // 使用比较器比较当前元素 与它的根节点大小,当前元素大则进入循环体
  31. while (comp.compare(heap.get(index), heap.get((index - 1) / 2)) < 0) {
  32. swap(index, (index - 1) / 2);
  33. index = (index - 1) / 2;
  34. }
  35. }
  36. public void swap(int i, int j) {
  37. T o1 = heap.get(i); // 获取值
  38. T o2 = heap.get(j);
  39. heap.set(i, o2); // 增加替换指定索引
  40. heap.set(j, o1);
  41. indexMap.put(o2, i);// 更新反向索引
  42. indexMap.put(o1, j);
  43. }
  44. public T pop() { // 删除并返回堆顶
  45. T ans = heap.get(0);
  46. swap(0, heapSize - 1); // 堆顶与尾元素交换
  47. indexMap.remove(ans); // 删除索引
  48. heap.remove(--heapSize); // 与尾元素断开连接
  49. heapify(0);
  50. return ans;
  51. }
  52. // 下沉
  53. private void heapify(int index) {
  54. int left = index * 2 + 1;
  55. while (left < heapSize) {
  56. // 查找左右孩子最大值下标
  57. int best = left + 1 < heapSiz e && comp.compare(heap.get(left + 1), heap.get(left)) < 0 ? (left + 1) : left;
  58. best = comp.compare(heap.get(best), heap.get(index)) < 0 ? best : index;
  59. if (best == index) {
  60. break;
  61. }
  62. // 与当前节点交换
  63. swap(best, index);
  64. index = best; // 继续下沉
  65. left = index * 2 + 1;
  66. }
  67. }
  68. // 从堆中删除用户给定的值 并保存堆完整性
  69. public void remove(T obj) {
  70. T replace = heap.get(heapSize - 1); // 获取尾元素
  71. int index = indexMap.get(obj); // 获取要删除的索引
  72. indexMap.remove(obj); // 删除索引
  73. heap.remove(--heapSize); // 删除尾元素 前面已经缓存了尾元素值
  74. if (obj != replace) {
  75. heap.set(index, replace); // 直接更新堆替换为尾元素
  76. indexMap.put(replace, index); // 更新索引表
  77. resign(replace);
  78. }
  79. }
  80. // 更新了一个堆中元素 保存堆完整性
  81. public void resign(T replace) {
  82. heapInsert(indexMap.get(replace)); // 上移
  83. heapify(indexMap.get(replace)); // 下沉总会发生一个事件
  84. }
  85. public List<T> getAllElements() {
  86. List<T> ans = new ArrayList<>();
  87. for (T t : heap) {
  88. ans.add(t);
  89. }
  90. return ans;
  91. }
  92. }

5.1. 加强堆面试题

  1. 做一个加强堆的题目,给定一个整型数组,int[] arr;和一个布尔类型数组,boolean[] op
  2. 两个数组一定等长,假设长度为Narr[i]表示客户编号,op[i]表示客户操作
  3. arr= [3,3,1,2,1,2,5
  4. op = [T,T,T,T,F,T,F
  5. 依次表示:
  6. 3用户购买了一件商品
  7. 3用户购买了一件商品
  8. 1用户购买了一件商品
  9. 2用户购买了一件商品
  10. 1用户退货了一件商品
  11. 2用户购买了一件商品
  12. 5用户退货了一件商品…
  13. 一对arr[i]和op[i]就代表一个事件:
  14. 用户号为arr[i],op[i] == T就代表这个用户购买了一件商品
  15. op[i] == F就代表这个用户退货了一件商品
  16. 现在你作为电商平台负责人,你想在每一个事件到来的时候,
  17. 都给购买次数最多的前K名用户颁奖。
  18. 所以每个事件发生后,你都需要一个得奖名单(得奖区)。
  19. 得奖系统的规则:
  20. 1,如果某个用户购买商品数为0,但是又发生了退货事件,
  21. 则认为该事件无效,得奖名单和上一个事件发生后一致,例子中的5用户
  22. 2,某用户发生购买商品事件,购买商品数+1,发生退货事件,购买商品数-1
  23. 3,每次都是最多K个用户得奖,K也为传入的参数
  24. 如果根据全部规则,得奖人数确实不够K个,那就以不够的情况输出结果
  25. 4,得奖系统分为得奖区和候选区,任何用户只要购买数>0
  26. 一定在这两个区域中的一个
  27. 5,购买数最大的前K名用户进入得奖区,
  28. 在最初时如果得奖区没有到达K个用户,那么新来的用户直接进入得奖区
  29. 6,如果购买数不足以进入得奖区的用户,进入候选区
  30. 7,如果候选区购买数最多的用户,已经足以进入得奖区,
  31. 该用户就会替换得奖区中购买数最少的用户(大于才能替换),
  32. 如果得奖区中购买数最少的用户有多个,就替换最早进入得奖区的用户
  33. 如果候选区中购买数最多的用户有多个,机会会给最早进入候选区的用户
  34. 8,候选区和得奖区是两套时间,
  35. 因用户只会在其中一个区域,所以只会有一个区域的时间,另一个没有
  36. 从得奖区出来进入候选区的用户,得奖区时间删除,
  37. 进入候选区的时间就是当前事件的时间(可以理解为arr[i]和op[i]中的i
  38. 从候选区出来进入得奖区的用户,候选区时间删除,
  39. 进入得奖区的时间就是当前事件的时间(可以理解为arr[i]和op[i]中的i
  40. 9,如果某用户购买数==0,不管在哪个区域都离开,区域时间删除,
  41. 离开是指彻底离开,哪个区域也不会找到该用户
  42. 如果下次该用户又发生购买行为,产生>0的购买数,
  43. 会再次根据之前规则回到某个区域中,进入区域的时间重记
  44. 请遍历arr数组和op数组,遍历每一步输出一个得奖名单
  45. public List<List<Integer>> topK (int[] arr, boolean[] op, int k)
  1. // 封装对象
  2. public static class Customer {
  3. public int id;
  4. public int buy;
  5. public int enterTime;
  6. public Customer(int id, int buy, int enterTime) {
  7. this.id = id;
  8. this.buy = buy;
  9. this.enterTime = enterTime;
  10. }
  11. }
  12. public static class CandidateComparator implements Comparator<Customer> {
  13. @Override
  14. public int compare(Customer o1, Customer o2) {
  15. // 如果待选区的购买时间相同 则以购买金额比较(从大到小排序 第一个元素为最大值) 否则以时间则比较
  16. return o1.buy != o2.buy ? (o2.buy - o1.buy) : (o1.enterTime - o2.enterTime);
  17. }
  18. }
  19. public static class DaddyComparator implements Comparator<Customer> {
  20. @Override
  21. public int compare(Customer o1, Customer o2) {
  22. // 如果待选区的购买时间相同 则以购买金额比较(从小到大排序 第一个元素为最小值) 否则以时间则比较
  23. return o1.buy != o2.buy ? (o1.buy - o2.buy) : (o1.enterTime - o2.enterTime);
  24. }
  25. }
  26. public static class WhoYourDaddy {
  27. private HashMap<Integer, Customer> customers; // 反向索引表
  28. private HeapGreater<Customer> candHeap; // 待选区
  29. private HeapGreater<Customer> daddyHeap; // 得奖区
  30. private final int daddyLimit; // 得奖区大小
  31. public WhoYourDaddy(int daddyLimit) {
  32. this.daddyLimit = daddyLimit;
  33. customers = new HashMap<>();
  34. candHeap = new HeapGreater<>(new CandidateComparator());
  35. ;
  36. daddyHeap = new HeapGreater<>(new DaddyComparator());
  37. }
  38. public void operate(int time, int id, boolean buyOrRefund) {
  39. // 退货操作 并且索引表(即两个区都没有记录)没有 不用进行扣减不存在负数的记录
  40. if (!buyOrRefund && !customers.containsKey(id)) {
  41. return;
  42. }
  43. // 两个区都不存在此记录 先精灵索引
  44. if (!customers.containsKey(id)) {
  45. // 以id为索引 初始化值后面修改
  46. customers.put(id, new Customer(id, 0, 0));
  47. }
  48. Customer c = customers.get(id); // 以id在索引表查找出对象 进行修改
  49. if (buyOrRefund) {
  50. // 是购买记录(即为T) 购买数++
  51. c.buy++;
  52. } else {
  53. // 是退货记录
  54. c.buy--;
  55. }
  56. if (c.buy == 0) {
  57. // 如果目前对象进行增减操作后购买数为仍0 则要移除 从索引表删除
  58. customers.remove(id);
  59. }
  60. //多种情况分别讨论
  61. if (!candHeap.contains(c) && !daddyHeap.contains(c)) {
  62. //此对象 待选区和得奖区都不存在
  63. //得奖区有空闲位置 直接丢入得奖区
  64. if(daddyHeap.size() < daddyLimit) {
  65. c.enterTime = time;
  66. daddyHeap.push(c);
  67. } else {
  68. //得奖区已满 丢人待选区
  69. c.enterTime = time;
  70. candHeap.push(c);
  71. }
  72. } else if(candHeap.contains(c)) {
  73. //此对象 存在于待选区
  74. if(c.buy == 0) {
  75. //为0 从待选区移除
  76. candHeap.remove(c);
  77. } else {
  78. //进行 上移和下沉操作 重新保持堆的完整性
  79. candHeap.resign(c);
  80. }
  81. } else {
  82. //此对象 存在于得奖区
  83. if(c.buy == 0) {
  84. daddyHeap.remove(c);
  85. } else {
  86. daddyHeap.resign(c);
  87. }
  88. }
  89. daddyMove(time); //本次添加对象已经整合完毕 进行待选区与得奖区的比较
  90. }
  91. private void daddyMove(int time) {
  92. //待选区为空 结束操作
  93. if(candHeap.isEmpty()) {
  94. return;
  95. }
  96. //如果得奖区还有空位 则以待选区 堆顶元素 丢入得奖区
  97. if(daddyHeap.size() < daddyLimit) {
  98. Customer p = candHeap.pop(); //删除并获取待选区堆顶
  99. p.enterTime = time; //重新设置当前为当前时间
  100. daddyHeap.push(p); //丢入得奖区
  101. } else {
  102. //比较 待选区最大值 与 得奖区最小值的大小
  103. if(candHeap.peek().buy > daddyHeap.peek().buy) {
  104. Customer oldDaddy = daddyHeap.pop(); //得奖区最小值
  105. Customer newDaddy = candHeap.pop(); //待选区最大值
  106. oldDaddy.enterTime = time; //重置时间
  107. newDaddy.enterTime = time;
  108. daddyHeap.push(newDaddy); //将待选区最大值丢入得奖区
  109. candHeap.push(oldDaddy); //将得奖区最小值丢回待选区
  110. }
  111. }
  112. }
  113. //获取得奖区的全部id(用户)
  114. public List<Integer> getDaddies(){
  115. List<Customer> customers = daddyHeap.getAllElements(); //获取得奖区的全部对象
  116. List<Integer> ans = new ArrayList<>();
  117. for (Customer c : customers) {
  118. ans.add(c.id);
  119. }
  120. return ans;
  121. }
  122. }
  123. public static List<List<Integer>> topK(int[] arr,boolean[] op,int k){
  124. List<List<Integer>> ans = new ArrayList<>();
  125. WhoYourDaddy whoDaddies = new WhoYourDaddy(k);
  126. for (int i = 0; i < arr.length; i++) {
  127. whoDaddies.operate(i, arr[i], op[i]);
  128. ans.add(whoDaddies.getDaddies());
  129. }
  130. return ans;
  131. }
  132. // 暴力破解法
  133. // 干完所有的事,模拟,不优化
  134. public static List<List<Integer>> compare(int[] arr, boolean[] op, int k) {
  135. HashMap<Integer, Customer> map = new HashMap<>();
  136. ArrayList<Customer> cands = new ArrayList<>();
  137. ArrayList<Customer> daddy = new ArrayList<>();
  138. List<List<Integer>> ans = new ArrayList<>();
  139. for (int i = 0; i < arr.length; i++) {
  140. int id = arr[i];
  141. boolean buyOrRefund = op[i];
  142. if (!buyOrRefund && !map.containsKey(id)) {
  143. ans.add(getCurAns(daddy));
  144. continue;
  145. }
  146. // 没有发生:用户购买数为0并且又退货了
  147. // 用户之前购买数是0,此时买货事件
  148. // 用户之前购买数>0, 此时买货
  149. // 用户之前购买数>0, 此时退货
  150. if (!map.containsKey(id)) {
  151. map.put(id, new Customer(id, 0, 0));
  152. }
  153. // 买、卖
  154. Customer c = map.get(id);
  155. if (buyOrRefund) {
  156. c.buy++;
  157. } else {
  158. c.buy--;
  159. }
  160. if (c.buy == 0) {
  161. map.remove(id);
  162. }
  163. // c
  164. // 下面做
  165. if (!cands.contains(c) && !daddy.contains(c)) {
  166. if (daddy.size() < k) {
  167. c.enterTime = i;
  168. daddy.add(c);
  169. } else {
  170. c.enterTime = i;
  171. cands.add(c);
  172. }
  173. }
  174. cleanZeroBuy(cands);
  175. cleanZeroBuy(daddy);
  176. cands.sort(new CandidateComparator());
  177. daddy.sort(new DaddyComparator());
  178. move(cands, daddy, k, i);
  179. ans.add(getCurAns(daddy));
  180. }
  181. return ans;
  182. }
  183. public static void move(ArrayList<Customer> cands, ArrayList<Customer> daddy, int k, int time) {
  184. if (cands.isEmpty()) {
  185. return;
  186. }
  187. // 候选区不为空
  188. if (daddy.size() < k) {
  189. Customer c = cands.get(0);
  190. c.enterTime = time;
  191. daddy.add(c);
  192. cands.remove(0);
  193. } else { // 等奖区满了,候选区有东西
  194. if (cands.get(0).buy > daddy.get(0).buy) {
  195. Customer oldDaddy = daddy.get(0);
  196. daddy.remove(0);
  197. Customer newDaddy = cands.get(0);
  198. cands.remove(0);
  199. newDaddy.enterTime = time;
  200. oldDaddy.enterTime = time;
  201. daddy.add(newDaddy);
  202. cands.add(oldDaddy);
  203. }
  204. }
  205. }
  206. public static void cleanZeroBuy(ArrayList<Customer> arr) {
  207. List<Customer> noZero = new ArrayList<Customer>();
  208. for (Customer c : arr) {
  209. if (c.buy != 0) {
  210. noZero.add(c);
  211. }
  212. }
  213. arr.clear();
  214. for (Customer c : noZero) {
  215. arr.add(c);
  216. }
  217. }
  218. public static List<Integer> getCurAns(ArrayList<Customer> daddy) {
  219. List<Integer> ans = new ArrayList<>();
  220. for (Customer c : daddy) {
  221. ans.add(c.id);
  222. }
  223. return ans;
  224. }
  225. // 为了测试
  226. public static class Data {
  227. public int[] arr;
  228. public boolean[] op;
  229. public Data(int[] a, boolean[] o) {
  230. arr = a;
  231. op = o;
  232. }
  233. }
  234. // 为了测试
  235. public static Data randomData(int maxValue, int maxLen) {
  236. int len = (int) (Math.random() * maxLen) + 1;
  237. int[] arr = new int[len];
  238. boolean[] op = new boolean[len];
  239. for (int i = 0; i < len; i++) {
  240. arr[i] = (int) (Math.random() * maxValue);
  241. op[i] = Math.random() < 0.5 ? true : false;
  242. }
  243. return new Data(arr, op);
  244. }
  245. // 为了测试
  246. public static boolean sameAnswer(List<List<Integer>> ans1, List<List<Integer>> ans2) {
  247. if (ans1.size() != ans2.size()) {
  248. return false;
  249. }
  250. for (int i = 0; i < ans1.size(); i++) {
  251. List<Integer> cur1 = ans1.get(i);
  252. List<Integer> cur2 = ans2.get(i);
  253. if (cur1.size() != cur2.size()) {
  254. return false;
  255. }
  256. cur1.sort((a, b) -> a - b);
  257. cur2.sort((a, b) -> a - b);
  258. for (int j = 0; j < cur1.size(); j++) {
  259. if (!cur1.get(j).equals(cur2.get(j))) {
  260. return false;
  261. }
  262. }
  263. }
  264. return true;
  265. }
  266. public static void main(String[] args) {
  267. int maxValue = 10;
  268. int maxLen = 100;
  269. int maxK = 6;
  270. int testTimes = 100000;
  271. System.out.println("测试开始");
  272. for (int i = 0; i < testTimes; i++) {
  273. Data testData = randomData(maxValue, maxLen);
  274. int k = (int) (Math.random() * maxK) + 1;
  275. int[] arr = testData.arr;
  276. boolean[] op = testData.op;
  277. List<List<Integer>> ans1 = topK(arr, op, k);
  278. List<List<Integer>> ans2 = compare(arr, op, k);
  279. if (!sameAnswer(ans1, ans2)) {
  280. for (int j = 0; j < arr.length; j++) {
  281. System.out.println(arr[j] + " , " + op[j]);
  282. }
  283. System.out.println(k);
  284. System.out.println(ans1);
  285. System.out.println(ans2);
  286. System.out.println("出错了!");
  287. break;
  288. }
  289. }
  290. System.out.println("测试结束");
  291. }

6. 23. 合并K个升序链表

  1. public class ListNode {
  2. int val;
  3. ListNode next;
  4. ListNode() {
  5. }
  6. ListNode(int val) {
  7. this.val = val;
  8. }
  9. ListNode(int val, ListNode next) {
  10. this.val = val;
  11. this.next = next;
  12. }
  13. }
  14. public ListNode mergeKLists(ListNode[] lists) {
  15. PriorityQueue<ListNode> heap = new PriorityQueue<ListNode>((o1, o2) -> o1.val - o2.val); //自定义比较器的最小堆
  16. for (ListNode listNode : lists) { //遍历链表数组
  17. if (listNode != null) {
  18. heap.add(listNode); //非空则push到最小堆
  19. }
  20. }
  21. if (heap.isEmpty()) { //堆中没有
  22. return null;
  23. }
  24. ListNode head = heap.poll(); // 弹出最小堆头
  25. ListNode pre = head;
  26. if (pre.next != null) {
  27. heap.add(pre.next); // 如果pre下一跳 不为空 直接push到最小堆中
  28. }
  29. while (!heap.isEmpty()) { //最小堆不为空
  30. ListNode cur = heap.poll(); //弹出栈顶 即最小值
  31. pre.next = cur; //下一跳 连接
  32. pre = cur; //更新当前节点
  33. if (cur.next != null) { //当前节点 下一跳不为空则添加到最小堆中
  34. heap.add(cur.next);
  35. }
  36. }
  37. return head;
  38. }