[路飞]_leetcode刷题_451. 根据字符出现频率排序

154 阅读2分钟

题目

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:

暴力解法。

  1. 申明一个哈希表,存储字符和它出现的次数
  2. 将哈希表转成数组,再用sort排序
  3. 遍历数组,将所有字符串拼接起来

代码如下:

/**
 * @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:

大顶堆。

  1. 申明一个哈希表,存储字符和它出现的次数
  2. 申明一个大顶堆,并把哈希表的每一项都推入堆内
  3. 新建一个结果数组,依次将堆内元素弹出push到数组里
  4. 拼接数组元素得到结果
/**
 * @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)