剑指 Offer 40. 最小的k个数
题目:
思路:
【1】简单点的思路就是先排序,再取出K个最小的值,如果没有做限制的话,使用sort函数就好了,如果限制函数的话,其实可以考虑快速排序和选择排序,快排的话不一定比选择排序更优,如果K很小的话其实选择排序会比快排更快,不过冒泡和选择两种排序的时间复杂度都是O(N)。但是解答时候明显使用快排的话比使用内置函数要快。
【2】使用小顶堆的方式:但其实JAVA里面并没有堆这个结构,一般都是二叉树的自我实现,其实大小顶堆可以参考 PriorityQueue 优先队列的源码,通过上浮和下沉的方式,利用满二叉树的概念构建的大小顶堆来形成优先队列的概念。
代码展示:
使用堆概念的方法:
//时间15 ms击败29.2% //内存42.9 MB击败18.97% //这种是全部塞入,然后根据多少来取出,因为本身就是小顶堆 class Solution { public int[] getLeastNumbers(int[] arr, int k) { if (k == 0) return new int[0]; int v[] = new int[k]; PriorityQueue<Integer> pq = new PriorityQueue<>(); for (int i = 0; i < arr.length; i++) { pq.offer(arr[i]); } for (int i = 0; i < k; i++) { v[i] = pq.poll(); } return v; } } //时间16 ms击败25.23% //内存43.1 MB击败10.76% //时间复杂度:O(nlogk),其中 n 是数组 arr 的长度。由于大根堆实时维护前 kkk 小值,所以插入删除都是 O(logk) 的时间复杂度,最坏情况下数组里 n 个数都会插入,所以一共需要 O(nlogk) 的时间复杂度。 //空间复杂度:O(k),因为大根堆里最多 k 个数。 //这种是构建大顶堆,所以塞入的个数其实是K个,本身也能达到目的 class Solution { public int[] getLeastNumbers(int[] arr, int k) { int[] vec = new int[k]; if (k == 0) { // 排除 0 的情况 return vec; } PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() { public int compare(Integer num1, Integer num2) { return num2 - num1; } }); //利用java8函数表达式的简化写法 //PriorityQueue<Integer> queue = new PriorityQueue<Integer>((a, b) -> (b - a)); for (int i = 0; i < k; ++i) { queue.offer(arr[i]); } for (int i = k; i < arr.length; ++i) { if (queue.peek() > arr[i]) { queue.poll(); queue.offer(arr[i]); } } for (int i = 0; i < k; ++i) { vec[i] = queue.poll(); } return vec; } }
使用快排的方式:
//时间2 ms击败93.24% //内存42.7 MB击败32.16% //时间复杂度:期望为 O(n) ,最坏情况下的时间复杂度为 O(n^2)。情况最差时,每次的划分点都是最大值或最小值,一共需要划分 n−1 次,而一次划分需要线性的时间复杂度,所以最坏情况下时间复杂度为 O(n^2)。 //空间复杂度:期望为 O(logn),递归调用的期望深度为 O(logn),每层需要的空间为 O(1),只有常数个变量。最坏情况下的空间复杂度为 O(n)。最坏情况下需要划分 n 次,即 randomized_selected 函数递归调用最深 n−1 层,而每层由于需要 O(1) 的空间,所以一共需要 O(n) 的空间复杂度 class Solution { public int[] getLeastNumbers(int[] arr, int k) { randomizedSelected(arr, 0, arr.length - 1, k); int[] vec = new int[k]; for (int i = 0; i < k; ++i) { vec[i] = arr[i]; } return vec; } private void randomizedSelected(int[] arr, int l, int r, int k) { if (l >= r) { return; } int pos = randomizedPartition(arr, l, r); int num = pos - l + 1; if (k == num) { return; } else if (k < num) { randomizedSelected(arr, l, pos - 1, k); } else { randomizedSelected(arr, pos + 1, r, k - num); } } // 基于随机的划分 private int randomizedPartition(int[] nums, int l, int r) { int i = new Random().nextInt(r - l + 1) + l; swap(nums, r, i); return partition(nums, l, r); } private int partition(int[] nums, int l, int r) { int pivot = nums[r]; int i = l - 1; for (int j = l; j <= r - 1; ++j) { if (nums[j] <= pivot) { i = i + 1; swap(nums, i, j); } } swap(nums, i + 1, r); return i + 1; } private void swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } }
使用默认的函数进行排序:
//时间7 ms击败71.34% //内存41.9 MB击败84.9% //时间复杂度:O(nlogn),其中 n 是数组 arr 的长度。算法的时间复杂度即排序的时间复杂度。 //空间复杂度:O(logn),排序所需额外的空间复杂度为 O(logn)。 class Solution { public int[] getLeastNumbers(int[] arr, int k) { int[] vec = new int[k]; Arrays.sort(arr); for (int i = 0; i < k; ++i) { vec[i] = arr[i]; } return vec; } }