1. 算法面试题-排序元素移动小于k的数组

154 阅读3分钟

题目:

已知一个几乎有序的数组(几乎有序是指如果把数组排好序的话,每个元素移动的距离不超过k,且k相较于数组而言较小).请选择一个合适的算法对这个数据进行排序

分析:

  1. 使用小顶堆来解决该问题,它要使移动元素不超过k,那么就要将k+1个元素放入小顶堆。
  2. 将堆顶元素弹出放入数组对应的位置,比如第一次弹出的一定是最小的,(由小顶堆的性质可知,根节点一定小于左右节点的,但是左右节点大小就不一定类。) 然后为了保持堆的大小不变(主要是由于k的限制,所以堆的长度只能为k+1),第一个元素已经确定好了,故可以不用考虑,然后将第k+2个元素入堆,进行小根堆调整,保证满足小根堆条件。
  3. 当数组元素长度已经不满足k+1时,直接将heap中剩下的元素给拷贝到数组就行。

问题:

由于k的限制,故排序的结果是一个局部的有序,而不是全局有序。在保证k+1的大小的时候,可以让每次弹出的结果都是heap的堆顶,故有序,只要不满足k+1时,采取的直接拷贝,所以后续不一定有序,即使有比堆中更小的数也探测不到了,因为不满足移动元素不超过k。只能保证在不超过k的情况下移动到的最小位置。

举例:

现有数组 4,21,5,7,2,8,9并且假设k=3,模拟该算法的排序过程

  1. 由于k=3,故要将k+1=4个元素先进入heap,也就是4,21,5,7。他们通过add调整方法后在heap中的排序为:4, 7,5,21
  2. 将堆顶弹出,覆盖放入arr[0]的位置(前四个已经copy入heap里,覆盖即可),此时的heap经历过poll后会自行调整,heap中的排序为:5,7,21。 再把第k+2个元素,也就是2放入heap中,此时的heap为:2,5,21,7。同上述操作,依次弹出了2, 5。此时的数组已经遍历到了最后一个元素,也就要停止遍历,再往后就不满足长度为4的heap,此时的heap中排序为:7,8,21,9
  3. 将heap中所有的元素都打印出来,综上结果为4,2,5,7,8,21,9

代码:

/**
	* @description: <Describe the program>
	* @author: Trigger
	* @date: 2023/2/27 20:00 
	* @param: arr
	* @param: k
  */
public static void sortedArrDistanceLessK(int[] arr, int k){
  // 构建一个小根堆
  PriorityQueue<Integer> heap = new PriorityQueue<>();
  // index来遍历数组
  int index = 0;
  // 第一次遍历只把k+1个元素放入heap中,每次add都会进行调整
  // Math.min(arr.length-1, k)这是为了避免出现数组长度比k小的情况
  for (; index <= Math.min(arr.length-1, k); index++){
    heap.add(arr[index]);
  }
  // 第二次遍历开始从index往后进行遍历, 也就是k+1索引的元素开始
  int i = 0;
  // 先将堆顶进行弹出, 然后再把index位置给入堆, 每次弹出和加入堆都会进行调整
  for (; index < arr.length; i++, index++){
    arr[i] = heap.poll();
    heap.add(arr[index]);
  }
  // 当数组长度不满足条件时, 此时的元素肯定都已经入堆了
  // 直接将堆清空, 注意每次poll都会进行堆调整
  while (!heap.isEmpty()){
    arr[i++] = heap.poll();
  }

}