题目
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:
暴力解法。
- 申明一个哈希表,存储字符和它出现的次数
- 将哈希表转成数组,再用sort排序
- 遍历数组,将所有字符串拼接起来
代码如下:
/**
* @param {string} s
* @return {string}
*/
var frequencySort = function(s) {
let map = new Map();
let arr_s = s.split('');
for(let i=0;i<arr_s.length;i++){
if(map.has(arr_s[i])){
map.set(arr_s[i],map.get(arr_s[i])+1)
}else{
map.set(arr_s[i],1)
}
}
let arr = Array.from(map);
arr.sort((a,b)=>b[1]-a[1]);
let res = [];
for(let i=0;i<arr.length;i++){
res[i] = [];
res[i].length = arr[i][1];
res[i].fill(arr[i][0])
}
res = Array.prototype.concat.apply([],res)
return res.join('');
};
复杂度分析:
时间复杂度:O(nlogn),主要耗时在sort排序上,Array自带的排序,在chrome内,当元素大于10的时候,底层快速排序,所以时间复杂度为O(nlogn)
空间复杂度:O(n)
思路2:
大顶堆。
- 申明一个哈希表,存储字符和它出现的次数
- 申明一个大顶堆,并把哈希表的每一项都推入堆内
- 新建一个结果数组,依次将堆内元素弹出push到数组里
- 拼接数组元素得到结果
/**
* @param {string} s
* @return {string}
*/
var frequencySort = function(s) {
let map = new Map();
let arr_s = s.split('');
for(let i=0;i<arr_s.length;i++){
if(map.has(arr_s[i])){
map.set(arr_s[i],map.get(arr_s[i])+1)
}else{
map.set(arr_s[i],1)
}
}
let cmp = (a,b)=>a[1]<b[1]
let heap = new Heap(cmp);
let arr = Array.from(map);
for(let i=0;i<arr.length;i++){
heap.offer(arr[i])
}
let res = [];
let len = heap.size();
for(let i=0;i<len;i++){
res.push(heap.poll())
}
let result = [];
for(let i=0;i<res.length;i++){
result[i] = [];
result[i].length = res[i][1];
result[i].fill(res[i][0])
}
result = Array.prototype.concat.apply([],result)
return result.join('');
};
let defaultCmp =(a,b)=>{
return a[1] > b[1]
}
class Heap{
constructor(cmp=defaultCmp){
this.cmp = cmp;
this.heap = [];
}
offer(node){
this.heap.push(node);
this.siftUp(this.heap.length-1);
}
poll(){
let node = this.heap[0];
this.heap[0] = this.heap[this.heap.length-1];
this.heap.pop();
this.siftDown(0);
return node;
}
size(){
return this.heap.length;
}
peek(){
return this.heap[0];
}
siftUp(i){
// 上浮,直到到达顶点
while(i>0){
// 顶点下标是0开始的话,子节点为i,父节点为Math.floor((i-1)/2)
let parent = Math.floor((i-1)/2);
// 和父节点比较大小,如果父节点大就交换
if(this.cmp(this.heap[parent],this.heap[i])){
[this.heap[parent],this.heap[i]] = [this.heap[i],this.heap[parent]]
i = parent;
}else{
// 如果父节点更小的话,可以直接弹出循环了,再往上节点值只会更小
break;
}
}
}
siftDown(i){
// 顶点下表从0开始,那么左子节点下标为i*2+1
// 每次都判断当前的i是否有左子节点
while(i*2+1<this.heap.length){
// 根据i算出左子节点
let child = i*2+1;
// 如果右子节点存在,且小于左子节点的时候,我们取右子节点来跟当前节点比较
if(child+1<this.heap.length && this.cmp(this.heap[child],this.heap[child+1])){
child++;
}
// 比较当前节点和子节点的大小
if(this.cmp(this.heap[i],this.heap[child])){
[this.heap[i],this.heap[child]] = [this.heap[child],this.heap[i]]
i = child;
}else{
break;
}
}
}
}
复杂度分析
时间复杂度:Math.max(n,KlogK),哈希表的创建为O(n),假设不同字符数为K,那么入堆操作最坏为KlogK,所以复杂度为Math.max(n,KlogK)
空间复杂度:O(n)