刷题笔记系列
【刷题笔记01】- 数组
【刷题笔记02】-链表
【刷题笔记03 -哈希表】
【刷题笔记04】- 字符串
【刷题笔记05】-栈与队列
【刷题笔记06】树
1、 LeetCode215. 数组中的第K个最大元素
https://leetcode.cn/problems/kth-largest-element-in-an-array/description/
主要就是排序,然后取第K个最大元素。
- 堆排序
class HeapSort {
public int findKthLargest(int[] nums, int k) {
int heapSize = nums.length;
buildMaxHeap(nums, heapSize);
for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
// swap并--heapSize后就相当于把大根堆的最大值移除了
// 所以 i >= nums.length - k + 1, 移除k-1个后,就大根堆堆顶就是整个数组第k大的数了
swap(nums, 0, i);
--heapSize;
// 为什么这里调整堆不是从下往上了呢?
// 因为堆建好后已经确定父节点必然大于子节点,这里节点0是从堆底换上来的比较小的数,它拿上来只是起一个调整堆的作用
maxHeapify(nums, 0, heapSize);
}
return nums[0];
}
public void buildMaxHeap(int[] a, int heapSize) {
// 为什么是这样的访问顺序?
// ① 逆序是因为要 避免 出现调整子树有序后,最值移到子树顶端并比其父节点(已访问)大的情况。逆序访问,即使调整后,子树顺序不对了,也有办法重新调整
// ② i > heapSize/2 的都是叶子节点,无需考虑,所以起始 index 从 heapSize / 2开始
for (int i = heapSize / 2; i >= 0; --i) {
maxHeapify(a, i, heapSize);
}
}
public void maxHeapify(int[] a, int i, int heapSize) {
int lChildIndex = i * 2 + 1;
int rChildIndex = i * 2 + 2;
int largestIndex = i;
// 注意两次都是和 a[largest]比较,不是和 a[i] 比,要选取三者中的最大
if (lChildIndex < heapSize && a[lChildIndex] > a[largestIndex]) {
largestIndex = lChildIndex;
}
if (rChildIndex < heapSize && a[rChildIndex] > a[largestIndex]) {
largestIndex = rChildIndex;
}
if (largestIndex != i) {
swap(a, i, largestIndex);
// 交换后,调整和 i调换了最值的子树(因为可能a[i]比largest的孩子的值小
maxHeapify(a, largestIndex, heapSize);
}
}
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
- 快排
public int findKthLargest(int[] nums, int k) {
int target = nums.length - k;
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int index = quickSort(nums, left, right);
if (index == target) {
return nums[target];
} else if (index < target) {
left = index + 1;
} else {
right = index - 1;
}
}
return -1;
}
public int quickSort(int[] array, int left, int right) {
int pivot = array[left];
int startIndex = left;
left = left + 1;
// 因为是和pivot比较,所以即使left right重合了也要进入循环处理这个数
while (left <= right) {
while (left <= right && array[left] < pivot) {
left++;
}
while (left <= right && array[right] > pivot) {
right--;
}
if (left >= right) break;
swap(array, left, right);
left++;
right--;
}
// 最后一定是right在左 left在右,所以是交换nums[right]和 pivot
swap(array, startIndex, right);
return right;
}
public void swap(int[] array, int index1, int index2) {
// 这种不用额外空间的交换方式必须 index1 与 index2不相等,不然就是在同一个地址进行加法操作,结果自然不正确。
if (index1 == index2)
return;
array[index1] += array[index2];
array[index2] = array[index1] - array[index2];
array[index1] = array[index1] - array[index2];
}
2、LeetCode295. 数据流的中位数
https://leetcode.cn/problems/find-median-from-data-stream/description/
class MedianFinder {
// minQue 存放小于等于 中位数 的数据, 顺序
Queue<Integer> minQue;
// maxQue 存放大于 中位数 的数据, 逆序
Queue<Integer> maxQue;
public MedianFinder() {
minQue = new PriorityQueue<>((a,b)->(a-b));
maxQue = new PriorityQueue<>((a,b)->(b-a));
}
public void addNum(int num) {
if (minQue.isEmpty() || num <= minQue.peek()){
minQue.offer(num);
if(minQue.size() - maxQue.size() > 1){
minQue.offer(minQue.poll());
}
}else {
maxQue.offer(num);
if (maxQue.size() - minQue.size() > 0){
maxQue.offer(maxQue.poll());
}
}
}
public double findMedian() {
if (minQue.size() > maxQue.size()){
return minQue.peek();
}else {
if (minQue.size() == 0)
return 0;
return (maxQue.peek() + minQue.peek())/2.0;
}
}
}