第k大问题各类变种小结
声明
文章均为本人技术笔记,转载请注明出处:
[1] https://segmentfault.com/u/yzwall
[2] blog.csdn.net/j_dark/
1. 求第k大数
lintcode 5 Kth Largest Element
lintcode 606 Kth Largest Element II
lintcode 461 Kth Smallest Numbers in Unsorted Array(第k小问题,切分时与求第k大相反)
1.1 解法1:Partition $O(n)$复杂度解法
1.1.1 分治法思想
参考快速排序中的切分思想,切分花费$O(n)$时间,分治将问题规模缩小为$T(frac{n}{2})$
$$T(n) = O(n) + T(n / 2) = O(n)$$
求第k大数问题,找到轴点$pivot$,在区间[start, end]中,
切分(partition)操作:求第k大数问题,将所有比$pivot$小的放在$pivot左侧$,小的放在右侧(求第k小问题时相反);
切分完成后有$rightIndex + 1 = leftIndex$;
切分后区间[start, end]由三部分组成:
-
分治
第K大元素在$pivot$左边区间nums[start]~nums[rightIndex],缩小问题规模
第K大元素在$pivot$右边区间nums[leftIndex]~nums[end],缩小问题规模
第K大元素在nums[rightIndex]~nums[leftIndex]之间,直接返回
/**
* 解法1:快速排序切分思想求第K大元素,时间复杂度O(n),空间复杂度O(1)
* http://www.lintcode.com/zh-cn/problem/kth-largest-element/
* http://www.lintcode.com/zh-cn/problem/kth-largest-element-ii/
* @author yzwall
*/
class Solution {
public int kthLargestElement(int k, int[] nums) {
if (nums == null) {
return -1;
}
return quickSelect(nums, 0, nums.length - 1, k);
}
/**
* 快速排序切分思想求第k大元素,平均时间复杂度O(n)
*/
private int quickSelect(int[] nums, int start, int end, int k) {
if (start == end) {
return nums[start];
}
int leftIndex = start;
int rightIndex = end;
// 切分尽量均匀
int pivot = nums[start + (end - start) / 2];
while (leftIndex <= rightIndex) {
while (leftIndex <= rightIndex && nums[leftIndex] > pivot) {
leftIndex++;
}
while (leftIndex <= rightIndex && nums[rightIndex] < pivot) {
rightIndex--;
}
if (leftIndex <= rightIndex) {
int temp = nums[leftIndex];
nums[leftIndex] = nums[rightIndex];
nums[rightIndex] = temp;
leftIndex++;
rightIndex--;
}
}
// 第K大元素在pivot左边, 缩小问题规模
if (start + k - 1 <= rightIndex) {
return quickSelect(nums, start, rightIndex, k);
}
// 第K大元素在pivot右边, 缩小问题规模
if (start + k - 1 >= leftIndex) {
return quickSelect(nums, leftIndex, end, k - (leftIndex - start));
}
// 第K大元素在nums[rightIndex]~nums[leftIndex]之间
return nums[leftIndex - 1];
}
}
1.2 解法2:排序 $O(nlog n)$复杂度解法
升序排序后,直接输出第k大元素;
1.3 解法3:最小堆 $O(nlog n )$复杂度解法
将问题“求数组中第k大数”转换为“求数组中最大的k个数”
已知数组长度长度为$n$,维护节点总数为$k$的最小堆$heap$,堆顶即为第k大元素;
public int topK(int[] nums) {
Heap heap = new Heap();
for (int i = 0; i < nums.length; i++) {
if (heap.size() < k) {
heap.offer(nums[i]);
} else {
if (heap.top() < nums[i]){
heap.poll();
heap.offer(nums[i]);
}
}
}
return heap.top();
}
1.4 解法对比
--- | Partition解法1 | 排序常规解法2 | 最小堆解法3 |
---|---|---|---|
修改原数组 | 是 | 是 | 否 |
时间复杂度 | $O(n)$ | $O(nlog n)$ | $O(nlog k)$ |
空间复杂度 | $O(1)$ | $O(1)$ | $O(n)$ |
2 求第k小数
lintcode 461 Kth Smallest Numbers in Unsorted Array
求第k小数可用求第k大数三种解法解决,以下为$O(n)$复杂度代码
/**
* 求第k小问题,快速排序切分思想解决,时间复杂度O(n),空间复杂度O(1)
* http://www.lintcode.com/en/problem/kth-smallest-numbers-in-unsorted-array/
* @author yzwall
*/
class Solution {
public int kthSmallest(int k, int[] nums) {
if (nums == null) {
return -1;
}
return quickSelect(nums, 0, nums.length - 1, k);
}
private int quickSelect(int[] nums, int start, int end, int k) {
if (start == end) {
return nums[start];
}
int leftIndex = start;
int rightIndex = end;
int pivot = nums[start + (end - start) / 2];
while (leftIndex <= rightIndex) {
while (leftIndex <= rightIndex && nums[leftIndex] < pivot) {
leftIndex++;
}
while (leftIndex <= rightIndex && nums[rightIndex] > pivot) {
rightIndex--;
}
if (leftIndex <= rightIndex) {
int temp = nums[leftIndex];
nums[leftIndex] = nums[rightIndex];
nums[rightIndex] = temp;
leftIndex++;
rightIndex--;
}
}
if (start + k - 1 <= rightIndex) {
return quickSelect(nums, start, rightIndex, k);
}
if (start + k - 1 >= leftIndex) {
return quickSelect(nums, leftIndex, end, k - (leftIndex - start));
}
return nums[leftIndex - 1];
}
}
3 求中位数/求数组中出现次数超过一半的数字
lintcode 80 Median
核心思想:已知数组长度长度为$n$,中位数本质为:
1 从小到大第$[frac{n}{2}]+1$大个数;
2 数组中出现次数超过一半的数字(剑指offer_面试题29);
3.1 解法1:$O(nlog n)$复杂度解法
将数组升序排序(花费时间复杂度$O(nlog n)$),$O(1)$时间复杂度直接输出第$[frac{n}{2}]+1$个数
3.2 解法2:$O(n)$复杂度解法
根据LintCode80_Median题意,已知数组长度长度为$n$,中位数等同为第$[frac{n}{2}]+1$大个数,直接转换为求第k大数问题求解,无需排序,时间复杂度为$O(n)$;
/**
* 将求中位数转换为求第k大数问题,时间复杂度O(n)
* http://www.lintcode.com/zh-cn/problem/median/
* @author yzwall
*/
class Solution {
public int median(int[] nums) {
if (nums == null) {
return -1;
}
// 中位数是从大到小第N/2 + 1大数
int k = (nums.length / 2) + 1;
return quickSelect(nums, 0, nums.length - 1, k);
}
private int quickSelect(int[] nums, int start, int end, int k) {
if (start == end) {
return nums[start];
}
int leftIndex = start;
int rightIndex = end;
int pivot = nums[start + (end - start) / 2];
while (leftIndex <= rightIndex) {
while (leftIndex <= rightIndex && nums[leftIndex] > pivot) {
leftIndex++;
}
while (leftIndex <= rightIndex && nums[rightIndex] < pivot) {
rightIndex--;
}
if (leftIndex <= rightIndex) {
int temp = nums[leftIndex];
nums[leftIndex] = nums[rightIndex];
nums[rightIndex] = temp;
leftIndex++;
rightIndex--;
}
}
if (start + k - 1 <= rightIndex) {
return quickSelect(nums, start, rightIndex, k);
}
if (start + k - 1 >= leftIndex) {
return quickSelect(nums, leftIndex, end, k - (leftIndex - start));
}
return nums[leftIndex - 1];
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。