加强堆
系统提供的堆无法做到的事情:
1)已经入堆的元素,如果参与排序的指标(数据)方法变化,
系统提供的堆无法做到时间复杂度O(logN)调整!都是O(N)的调整!
需要先找到该数据对象,然后再对象进行调整。2)系统提供的堆只能弹出堆顶,做不到自由删除任何一个堆中的元素,
或者说,无法在时间复杂度O(logN)内完成!一定会高于O(logN)
根本原因:无反向索引表1)建立反向索引表
2)建立比较器
3)核心在于各种结构相互配合,非常容易出错
package com.lzf2.class05;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
//加强堆 T一定要是非基础类型(或者包一层)
public class HeapGreater<T> {
//堆
private ArrayList<T> heap;
//反向索引表
private HashMap<T,Integer> indexMap;
//堆大小
private int heapSize;
//比较器
private Comparator<? super T> comp;
public HeapGreater(Comparator<? super T> comp) {
heap = new ArrayList<>();
indexMap = new HashMap<>();
heapSize = 0;
this.comp = comp;
}
public boolean isEmpty(){
return heapSize == 0;
}
public int size(){
return heapSize;
}
public boolean contains(T obj){
return indexMap.containsKey(obj);
}
public T peek(){
return heap.get(0);
}
public void push(T obj){
heap.add(obj);
indexMap.put(obj,heapSize);
heapInsert(heapSize++);
}
public T pop(){
T ans = heap.get(0);
swap(0,heapSize - 1);
indexMap.remove(ans);
heap.remove(--heapSize);
heapify(0);
return ans;
}
public void remove(T obj){//O(logN)
T replace = heap.get(heapSize - 1);
int index = indexMap.get(obj);
indexMap.remove(obj);
heap.remove(--heapSize);
if(obj != replace){
heap.set(index,replace);
indexMap.put(replace,index);
resign(replace);
}
}
//obj对象发生改变,重新调整
public void resign(T obj){
heapInsert(indexMap.get(obj));
heapify(indexMap.get(obj));
}
public List<T> getAllElements(){
List<T> ans = new ArrayList<>();
for (T t : heap) {
ans.add(t);
}
return ans;
}
private void heapify(int index) {
int left = index * 2 + 1;
while (left < heapSize) {
int best = left + 1 < heapSize && comp.compare(heap.get(left + 1), heap.get(left)) < 0 ? (left + 1) : left;
best = comp.compare(heap.get(best), heap.get(index)) < 0 ? best : index;
if (best == index) {
break;
}
swap(best, index);
index = best;
left = index * 2 + 1;
}
}
private void heapInsert(int index) {
while (comp.compare(heap.get(index), heap.get((index - 1) / 2)) < 0) {
swap(index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
private void swap(int i, int j) {
T o1 = heap.get(i);
T o2 = heap.get(j);
heap.set(i, o2);
heap.set(j, o1);
indexMap.put(o2, i);
indexMap.put(o1, j);
}
}
第一题
问题描述:已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过k,并且k相对于数组长度来说是比较小的。请选择一个合适的排序策略,对这个数组进行排序。
提示:0-k放入小根堆,弹出 堆顶放到0位置就是对的
package com.lzf2.class05;
import java.util.Arrays;
import java.util.PriorityQueue;
//对几乎有序的数组排序
public class SortArrayDistanceLessK {
//排序
public static void sortedArrDistanceLessK(int[] arr, int k) {
if(k == 0){
return;
}
//1.准备一个小根堆
PriorityQueue<Integer> heap = new PriorityQueue<>();
//2.0 - k -1放入小根堆
int index = 0;
for (;index <= Math.min(arr.length - 1,k - 1);index++){
heap.add(arr[index]);
}
//堆中放入一个数,弹出一个数,直到数放完
int i = 0;
for (;index < arr.length;i++,index++){
heap.add(arr[index]);
arr[i] = heap.poll();
}
//3.堆中还剩k个数
while (!heap.isEmpty()){
arr[i++] = heap.poll();
}
}
// for test
public static void comparator(int[] arr, int k) {
Arrays.sort(arr);
}
// for test
public static int[] randomArrayNoMoveMoreK(int maxSize, int maxValue, int K) {
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());
}
// 先排个序
Arrays.sort(arr);
// 然后开始随意交换,但是保证每个数距离不超过K
// swap[i] == true, 表示i位置已经参与过交换
// swap[i] == false, 表示i位置没有参与过交换
boolean[] isSwap = new boolean[arr.length];
for (int i = 0; i < arr.length; i++) {
int j = Math.min(i + (int) (Math.random() * (K + 1)), arr.length - 1);
if (!isSwap[i] && !isSwap[j]) {
isSwap[i] = true;
isSwap[j] = true;
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
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) {
System.out.println("test begin");
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int k = (int) (Math.random() * maxSize) + 1;
int[] arr = randomArrayNoMoveMoreK(maxSize, maxValue, k);
int[] arr1 = copyArray(arr);
int[] arr2 = copyArray(arr);
sortedArrDistanceLessK(arr1, k);
comparator(arr2, k);
if (!isEqual(arr1, arr2)) {
succeed = false;
System.out.println("K : " + k);
printArray(arr);
printArray(arr1);
printArray(arr2);
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
}
}
第二题
问题描述:最大线段重合问题。给定很多线段,每个线段都有两个数[start, end],表示线段开始位置和结束位置,左右都是闭区间
规定:
1)线段的开始和结束位置一定都是整数值
2)线段重合区域的长度必须>=1
返回线段最多重合区域中,包含了几条线段[1,8] [4,8] 重合区域 [4,8]
提示:
每个线段根据开始位置排序 小->大
准备一个小根堆,存放线段的尾巴
遍历每条线段分别做以下几个操作
弹出小根堆中比当前线段头位置小的数
把当前线段的尾巴加到堆中
当前线段的重合数位堆中数的个数 和 之前求出来的最大重合数 比较。
比之前的大就替换,比之前小就继续看下一条线段
package com.lzf2.class05;
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
//最大线段重合问题
public class CoverMax {
//线段
public static class Line {
public int start;
public int end;
public Line(int s, int e) {
start = s;
end = e;
}
}
//开始位置的比较器 小 - 大
public static class StartComparator implements Comparator<Line>{
@Override
public int compare(Line o1, Line o2) {
return o1.start - o2.start;
}
}
//求最大线段重合
public static int maxCover(int[][] m){
//1.二位数组整成线段,并按开始位置从小到大排序
Line[] lines = new Line[m.length];
for (int i = 0; i < m.length; i++) {
lines[i] = new Line(m[i][0],m[i][1]);
}
Arrays.sort(lines,new StartComparator());//O(logn)
//2.准备一个小根堆,存储的是线段的结尾位置
PriorityQueue<Integer> heap = new PriorityQueue<>();
//3.遍历每个线段
int max= 0;
for (int i = 0; i < lines.length; i++) {//复杂读O(n * logn)
while (!heap.isEmpty() && heap.peek() <= lines[i].start){//调整代价O(logn)
heap.poll();
}
heap.add(lines[i].end);
max = Math.max(max,heap.size());
}
return max;
}
//对数器
public static int maxCover1(int[][] lines) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int i = 0; i < lines.length; i++) {
min = Math.min(min, lines[i][0]);
max = Math.max(max, lines[i][1]);
}
int cover = 0;
for (double p = min + 0.5; p < max; p += 1) {
int cur = 0;
for (int i = 0; i < lines.length; i++) {
if (lines[i][0] < p && lines[i][1] > p) {
cur++;
}
}
cover = Math.max(cover, cur);
}
return cover;
}
// for test
public static int[][] generateLines(int N, int L, int R) {
int size = (int) (Math.random() * N) + 1;
int[][] ans = new int[size][2];
for (int i = 0; i < size; i++) {
int a = L + (int) (Math.random() * (R - L + 1));
int b = L + (int) (Math.random() * (R - L + 1));
if (a == b) {
b = a + 1;
}
ans[i][0] = Math.min(a, b);
ans[i][1] = Math.max(a, b);
}
return ans;
}
public static void main(String[] args) {
Line l1 = new Line(4, 9);
Line l2 = new Line(1, 4);
Line l3 = new Line(7, 15);
Line l4 = new Line(2, 4);
Line l5 = new Line(4, 6);
Line l6 = new Line(3, 7);
// 底层堆结构,heap
PriorityQueue<Line> heap = new PriorityQueue<>(new StartComparator());
heap.add(l1);
heap.add(l2);
heap.add(l3);
heap.add(l4);
heap.add(l5);
heap.add(l6);
while (!heap.isEmpty()) {
Line cur = heap.poll();
System.out.println(cur.start + "," + cur.end);
}
System.out.println("test begin");
int N = 100;
int L = 0;
int R = 200;
int testTimes = 200000;
for (int i = 0; i < testTimes; i++) {
int[][] lines = generateLines(N, L, R);
int ans1 = maxCover1(lines);
int ans2 = maxCover(lines);
if (ans1 != ans2) {
System.out.println("Oops!");
}
}
System.out.println("test end");
}
}