小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
在前两篇文章我们学习了算法解题技巧: 双指针. 今天继续来学习一个进阶技巧, 分而治之解决问题: 数组中第k大元素.
分治法
分治法: 分而治之. 是计算机中重要的算法, 也是五大常用算法之一. 前文学到的二分法, 亦可以理解为简单的分治法的一种.
分治就是将复杂的问题分解成一个个小的问题, 逐个突破解决问题, 最后将各个小问题的结果合并起来, 从而得到复杂问题的答案.
分治法能解决那些问题
- 问题可以分解成多个若干小问题;
- 将复杂问题分解成多个小问题后, 每个小问题就比较容易解决;
- 复杂问题的解等于各个小问题的解的合并;
- 分解开的各个小问题相互独立.
数组中的第 k 大的元素
这个问题是在一个面试中循序渐进的问题, 由找数组中的最大元素 --> 第二大元素 --> 进而演化到 第k大;
我们来看下 LeetCode 215题: 先看图吧, 比较好理解, 但是解决问题就没有昨天那个问题那么简单了. LeetCode 定义为 中等.
题目描述:
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
1<=k<=数组长度
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
提示:
1 <= k <= nums.length <= 104-104 <= nums[i] <= 104
题解思路分析:
这个并不是找到第 k 个元素, 按照开始的题目难度演进, 查找最大元素和第二大元素, 就是不难理解, 可以使用排序降序排列, 就找到了答案. 所谓的暴力算法思路...(.也不是不可取.后面再优化嘛..)
1. 直接暴力解法:
直接排序, 找到最大的. 但是我们只是找到第 k 大的元素, 排序的话, 所有的元素都有排序了, 这并不需要!
暴力解法复杂度:
- 时间复制度:
O(nlogn), 取决于我们的排序函数 - 空间复杂度: 也是取决于排序函数
2. 使用 堆的思路,
第 k 大, 就是 k 堆.
在遍历数组过程中维护一个大小为 k 的最大堆, 遍历结束后 堆顶就是第 k 大元素.
3. 分治法: 快速选择
分析, 如下图:
我们选择 4 最为标定点, 在快排之后, 数组的形态就编程下面的样子了: 4 处在它正确的位置, 比 4 小的放前面, 比 4 大的放后面,
要寻找第二大的元素, 如果这个 4 返回的对应索引是在 第四位, 要在第二大的元素就在 >4 这个部分来继续寻找, 对于<4 的这部分就不需要找了.
JavaScript解题代码:
const findKthLargest = (nums, k) => {
const n = nums.length
const quick = (l, r) => {
if (l > r) return
let random = Math.floor(Math.random() * (r - l + 1)) + l
swap(nums, random, r)
/**
* 选定一个 pivot 元素,根据它进行 partition
* 通过 partition 后就找出一个位置: 它左边的元素都比 pivot 小,右边的元素都比 pivot 大
* 左边和右边的元素的是未排序的,但 pivotIndex 是确定下来的
*/
let pivotIndex = partition(nums, l, r)
if (n - k < pivotIndex) {
quick(l, pivotIndex - 1)
} else {
quick(pivotIndex + 1, r)
}
}
quick(0, n - 1)
return nums[n - k]
}
function partition(nums, left, right) {
let pivot = nums[right]
let pivotIndex = left
for (let i = left; i < right; i++) {
if (nums[i] < pivot) {
swap(nums, i, pivotIndex) // 交换
pivotIndex++
}
} // 循环结束时,pivotIndex左边都是比pivot小的
swap(nums, right, pivotIndex)
return pivotIndex
}
function swap(nums, p, q) {
const temp = nums[p]
nums[p] = nums[q]
nums[q] = temp
}
思考
我们遇到的问题总是看起来头大, 但是静下心来想一想, 可不可以将问题化解成若干个小问题, 分而治之, 找到解决问题的答案.