215. 数组中的第K个最大元素:TopK 快排问题
TopK 问题出现频率很高.
注意升降序和 (k-1)
- 求第 K 个最大元素,数组应该降序排列
- 算法优化:找个第 K 个元素后,递归就终止。
const divide = (arr, left, right) => { // 和快排的 divide() 一样
const pivot = arr[right];
let p = left;
for (let i = left; i < right; i++) {
if (arr[i] > pivot) { // 控制升序还是降序
[arr[i], arr[p]] = [arr[p], arr[i]];
p++;
}
}
[arr[right], arr[p]] = [arr[p], arr[right]];
return p;
}
// 不需要全部排完序,找到第K大的元素后终止递归!
const KthLargest = (nums, left = 0, right = nums.length - 1, k) => {
if (left < right) {
const mid = divide(nums, left, right);
if (mid == k - 1) { // 递归终止!
return; // 注意
} else if (mid < k - 1) {
KthLargest(nums, mid + 1, right, k);
} else {
KthLargest(nums, left, mid - 1, k);
}
}
}
const findKthLargest = (nums, k) => {
KthLargest(nums, 0, nums.length - 1, k);
return nums[k - 1];
}
拓展:获得前 K 大的数
const topkLargest = (nums, k) => {
KthLargest(nums, 0, nums.length - 1, k);
return nums.slice(0, k);
}
如果要求 前K小、第K小 的问题,那么将 divide 函数修改为升序即可!
56.合并区间:左端点 sort 排序
- 首先将区间按照左端点的大小升序排序;
- 然后遍历区间,根据上一个区间 prev 右端点和下一个区间左端点的大小关系,判断区间是否有重叠:
- 不重叠时,将上一个区间 push 进 res 中,并更新 prev;
- 重叠时,我们取两个区间右端点中的较大值来更新数值。
- 在遍历完成后,将最后一个区间 push 进 res 中。
const merge = function(nums) {
nums.sort((a, b) => a[0] - b[0]); // 按区间的左端点大小升序排列
const res = [];
let prev = nums[0];
for (let i = 1; i < nums.length; i++) {
if (prev[1] < nums[i][0]) { // 不重叠
res.push(prev);
prev = nums[i]; // 更新 prev
} else {
prev[1] = Math.max(prev[1], nums[i][1]);
}
}
res.push(prev); // 注意!!
return res;
};
时间复杂度:O(nlogn)。主要是 sort 的时间开销。
空间复杂度:O(logn)。sort 排序所需要的空间复杂度。
179. 最大数:拼接字典序sort + 贪心
sort 排序后得到的依然是整数数组;返回值要求是字符串!
- 对于 nums 中的任意两个值 a 和 b,我们无法直接从常规角度确定它们拼接之后的大小关系,但我们可以根据 结果 来决定 a 和 b 的排序关系。
- 如果拼接结果 ab 要比 ba 好,那么我们会认为 a 应该在 b 的前面。所以 我们要按照 「拼接结果的字典序大小」 排序。
- 另外,注意我们需要处理 前导0 (最多保留一位)。
const largestNumber = function(nums) {
nums.sort((a, b) => { // 按拼接结果的字典序大小排列
let s1 = `${a}${b}`;
let s2 = `${b}${a}`;
return s2 - s1; // 降序
});
// 要返回的是字符串,因此将数组转化为字符串
// 如果返回是 '00',那么直接返回一个 '0'即可。
return nums[0] ? '0' : nums.join('');
};
时间复杂度:O(nlogn)。主要来自于 sort 排序。