算法练习-排序算法
快速选择
215. 数组中的第K个最大元素(medium)
题目:给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 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 <= 105
-104 <= nums[i] <= 104
分析
这题是个典型的第几大的问题。不考虑时间复杂度的话,可以先排序再返回值。但是题目要求时间复杂度是O(n)。有一个经典的题型可以去套,第几大时,使用快速排序。但是有一个问题是,如果出现重复的值该怎麽办,这题根据示例来看,直接忽略这个问题,有重复的会把重复的也算进去。比如第二大,但是有两个重复的最大的,这时候选择最大值。
首先处理一下数值,第几大可以转换成数组里从左到右第几个,时nums.length-k对应的下标。
之后排序结合快速排序的方法,随便取一个值,这里取数组的左下标的值 为标记点。从数组右边开始比较,如果比目标值小,那右边指向的数赋值给左边指向的数,否则右边的指标向左移动。如果右边指标左移,开始找左边指标,看指向的值会不会大于目标值,如果大于就把左边指标指向的数,赋值给右边指标指向的数。直到左右指标重合,在重合的位置,赋值目标key。最后返回重合下标。这时,可以知道比k小的数有多少个了,与之前处理的下标对比,小了右移,大了左移。最后可以得到目标值。
var findKthLargest = function (nums, k) {
let target = nums.length - k;
let l = 0;
let r = nums.length - 1;
while (l < r) {
let mid = quickSort(nums, l, r);
if (mid == target) return nums[mid];
else if (mid < target) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return nums[l]
};
function quickSort(nums, left, right) {
let key = nums[left];
while (left < right) {
while (key < nums[right] && left < right) {
right--
}
nums[left] = nums[right];
while (nums[left] <= key && left < right) {
left++
}
nums[right] = nums[left]
}
nums[left] = key;
return left;
}
桶排序
347. 前 K 个高频元素(medium)
题目:给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。
分析
堆排序,核心点是,利用一个新的空间去记录当前的数组状态。这里可以先使用一个map去记录数组里每个item出现的频率。再对频率进行排序,最后输出前K个值就可以了。
var topKFrequent = function (nums, k) {
let map = new Map();
let arr = [...new Set(nums)]
for (let i = 0; i < nums.length; i++) {
let temp = nums[i]
if (map.has(temp)) {
map.set(temp, map.get(temp) + 1)
} else {
map.set(temp, 1)
}
}
return arr.sort((a, b) => map.get(b) - map.get(a)).slice(0, k)
};
练习
451. 根据字符出现频率排序(medium)
给定一个字符串 s ,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。
返回 已排序的字符串 。如果有多个答案,返回其中任何一个。
示例 1:
输入: s = "tree"
输出: "eert"
解释: 'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
示例 2:
输入: s = "cccaaa"
输出: "cccaaa"
解释: 'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:
输入: s = "Aabb"
输出: "bbAa"
解释: 此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。
提示:
1 <= s.length <= 5 * 105
s 由大小写英文字母和数字组成
分析
这题和上面的题目思路很相似,都是先记录数组里的状态,然后通过状态返回我们需要的数据。我们可以手动把字符串变成数组,然后采用上题相同的思想来做。是桶排序的变形
var frequencySort = function (s) {
let arr = s.split('');
let newarr = [... new Set(arr)]
let map = new Map();
arr.map(chart => {
if (map.has(chart)) {
map.set(chart, map.get(chart) + 1)
} else {
map.set(chart, 1)
}
})
newarr = newarr.sort((a, b) => map.get(b) - map.get(a));
let res = []
for (let item of newarr) {
res = res.concat(new Array(map.get(item)).fill(item))
}
return res.join("")
};
提升
75. 颜色分类(medium)
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2
进阶:
你能想出一个仅使用常数空间的一趟扫描算法吗?
分析
这题想让我们通过排序来写,可以选择的方法有冒泡、快速、选择、插入等方法。但是也可以换种方法,因为数组内的元素种类是已知的,所以采用桶排序只用遍历一次,然后根据出现的频率把数据依次填上去会快很多。
var sortColors = function (nums) {
let map = new Map();
nums.map((num) => {
if (map.has(num)) {
map.set(num, map.get(num) + 1)
} else {
map.set(num, 1)
}
})
let colors = [0, 1, 2]
let i = 0;
colors.map((color) => {
if (map.has(color)) {
let time = map.get(color)
while (time > 0) {
nums[i] = color;
time--;
i++
}
}
})
};