简单的排序算法
1 排序是最常见的算法,基本上每本书开头都是先介绍常用的各种排序算法。
排序算法总体来说,只是种类繁多,但是基本上都不是很难,都是些基础,这些多写,打好基础就可以了。
215 快速选择
题目描述
在一个未排序的数组中,找到第 k 大的数字。
注意,这里你可以默认肯定有解
例子1
输入: [3,2,1,5,6,4] and k = 2
输出: 5
例子2
输入: [3,2,3,1,2,4,5,5,6] and k = 4
输出: 4
思考 1
基本上先排序,然后直接输出就可以了
这里也可以自己实现,比如使用快速排序,一般快排是查找第k大数的常用方法
实现1
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
export default (nums, k) => {
nums.sort((a, b) => b - a);
return nums[k - 1];
};
实现2
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
const getPosition = (nums, p, r) => {
const mid = Math.floor(p + (r - p) / 2);
let temp;
let pos;
let tempArr = [nums[p], nums[r], nums[mid]].sort((a, b) => a - b);
temp = tempArr[1];
pos = temp === nums[p] ? p : temp === nums[r] ? r : mid;
for (let i = p; i <= r; i++) {
if (nums[i] < temp && i > pos) {
const _temp = nums[i];
if (i > pos) {
for (let j = i; j > pos; j--) {
nums[j] = nums[j - 1];
}
}
nums[pos] = _temp;
pos++;
} else if (nums[i] > temp && i < pos) {
nums[pos] = nums[i];
nums[i] = temp;
pos = i;
}
}
nums[pos] = temp;
return pos;
};
// p 起始位置, r 结束位置
// 3, 2, 3, 1, 2, 4, 5, 5, 6
const quickSort = (nums, p, r) => {
if (p < r) {
const q = getPosition(nums, p, r);
quickSort(nums, p, q - 1);
quickSort(nums, q + 1, r);
}
};
export default (nums, k) => {
// nums.sort((a, b) => a - b);
quickSort(nums, 0, nums.length - 1);
return nums[nums.length - k];
};
实现1的算法时间复杂度 O(nlgn), 空间复杂度 O(1)
451. 根据字符出现频率排序
题目描述
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
例子1
输入: "tree"
输出: "eert"
解释: 'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案
例子2
输入: "cccaaa"
输出: "cccaaa"
解释: 'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。注意"cacaca"是不正确的,因为相同的字母必须放在一起。
例子3
输入: "Aabb"
输出: "bbAa"
解释: 此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。
思考 1
题目也比较简单,只要思想类似于桶排序,我们先用一个hashMap存储每个字符出现的次数,然后对hashMap进行排序,最后再取出来就可以了。
实现1
/**
* @param {string} s
* @return {string}
*/
export default (s) => {
const map = new Map();
for (let i = 0; i < s.length; i++) {
if (!map.has(s[i])) {
map.set(s[i], 1);
} else {
const count = map.get(s[i]) + 1;
map.set(s[i], count);
}
}
let res = [];
for (let [key, value] of map) {
res.push({
key,
value,
});
}
res.sort((a, b) => b.value - a.value);
let res1 = "";
for (let i = 0; i < res.length; i++) {
res1 += res[i].key.repeat(res[i].value);
}
return res1;
};
实现1的算法时间复杂度 O(nlgn), 空间复杂度 O(1)
这里空间复杂度是O(1)的原因是最多只有26个字母,所以不会一直扩张
347. 前 K 个高频元素
题目描述
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
例子1
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
例子2
输入: nums = [1], k = 1
输出: [1]
提示:
1 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
2 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
3 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
4 你可以按任意顺序返回答案。
思考 1
桶排序顾名思义就是我们事先放几个桶,这几个桶是排好序的,然后我们可以把符合每个桶的元素放到每个桶里边,这样就天然变成有序的了
这里我们可以根据数组中每个不同的数字分别设置一个桶,每个桶里放入数字出现的次序,最后返回钱k个高频元素就可以了
实现1
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
export default (nums, k) => {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
if (!map.has(nums[i])) {
map.set(nums[i], 1);
} else {
const count = +map.get(nums[i]) + 1;
map.set(nums[i], count);
}
}
let res = [];
for (let [key, value] of map) {
res.push({
key,
value,
});
}
res.sort((a, b) => b.value - a.value);
res = res.slice(0, k);
return res.map((item) => +item.key);
};
实现1的算法时间复杂度 O(nlgn), 空间复杂度 O(1)
75. 颜色分类
题目描述
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
进阶:
1 你可以不使用代码库中的排序函数来解决这道题吗?
2 你能想出一个仅使用常数空间的一趟扫描算法吗?
例子1
输入: nums = [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
例子2
输入: nums = [2,0,1]
输出: [0,1,2]
例子3
输入: nums = [0]
输出: [0]
例子4
输入: nums = [1]
输出: [1]
提示:
1 n == nums.length
2 1 <= n <= 300
3 nums[i] 为 0、1 或 2
思考 1
这里使用桶排序,直接统计0,1,2出现的次数,直接修改原数组就可以了
当然这里还有其它方法,比如使用双指针,因为前面使用过双指针,我们可以实现下
双指针也很简单,一个low指向数组的开始,一个high指向数组的末尾,使用i进行遍历数组,如果发现nums[i]等于0,则和low交换,并且low++,当发现等于2,则和high交换并且high--
实现1
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
// [2, 0, 2, 1, 1, 0][(2, 0, 2, 1, 1, 0)];
export default (nums) => {
const res = new Array(3).fill(0);
for (let i = 0; i < nums.length; i++) {
if (nums[i] === 0) {
res[0]++;
} else if (nums[i] === 1) {
res[1]++;
} else {
res[2]++;
}
}
// console.log(res);
for (let i = 0; i < res[0]; i++) {
nums[i] = 0;
}
for (let i = 0; i < res[1]; i++) {
nums[res[0] + i] = 1;
}
for (let i = 0; i < res[2]; i++) {
nums[res[0] + i + res[1]] = 2;
}
};
实现2
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
// [2, 0, 2, 1, 1, 0][(2, 0, 2, 1, 1, 0)];
const swap = (nums, i, j) => {
const temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
};
export default (nums) => {
let low = 0;
while (nums[low] === 0) {
low++;
}
let high = nums.length - 1;
while (nums[high] === 2) {
high--;
}
for (let i = low; i <= high; ) {
if (low > high) {
break;
}
if (nums[i] === 0 || nums[i] === 2) {
if (nums[i] === 0) {
if (low !== i) {
swap(nums, low, i);
}
low++;
i = low;
} else if (nums[i] === 2) {
if (i !== high) {
swap(nums, i, high);
}
high--;
}
continue;
} else {
i++;
}
}
return nums;
};
实现1的算法时间复杂度 O(n), 空间复杂度 O(1)
实现2的算法时间复杂度 O(n), 空间复杂度 O(1)
排序算法总结
现实中使用的排序算法其实不多,大多数是作为基础,考验个人的细心和基础,这些一般都是作为基础算法,来磨练基本功的。
基本上了解其思想就可以。