• 数据结构与算法之堆: Leetcode 451. 根据字符出现频率排序 (Typescript版)


    根据字符出现频率排序

    • https://leetcode.cn/problems/sort-characters-by-frequency/

    描述

    • 给定一个字符串 s ,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。
    • 返回 已排序的字符串 。如果有多个答案,返回其中任何一个。

    示例 1

    输入: s = "tree"
    输出: "eert"
    解释: 'e'出现两次,'r'和't'都只出现一次。
    因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
    
    • 1
    • 2
    • 3
    • 4

    示例 2

    输入: s = "cccaaa"
    输出: "cccaaa"
    解释: 'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
    注意"cacaca"是不正确的,因为相同的字母必须放在一起。
    
    • 1
    • 2
    • 3
    • 4

    示例 3

    输入: s = "Aabb"
    输出: "bbAa"
    解释: 此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
    注意'A'和'a'被认为是两种不同的字符。
    
    • 1
    • 2
    • 3
    • 4

    提示

    • 1 <= s.length <= 5 * 1 0 5 10^5 105
    • s 由大小写英文字母和数字组成

    算法实现

    1 )普通方法实现, 基于原生sort和Map结构

    function frequencySort(s: string): string {
        // 1. 构建map字典,例如: Map{a: 2, b: 3}
        const map = new Map();
        s.split('').forEach(item => {
          map.set(item, map.has(item) ? map.get(item) + 1 : 1);
        });
        // 2. 将map转成二维数组进行排序
        const arr = Array.from(map);
        arr.sort((a, b) => b[1] - a[1]);
        // 3. 基于排好序的数组(降序)组装成最终结果
        let result = '';
        arr.forEach((item) => {
            result += item[0].repeat(item[1]);
        })
        return result;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 这里使用平时最简单的原生排序法,结合Map数据结构的特性,和ES6中字符串的特性完成
    • 原生排序,性能不错 O(nlogn),推荐

    2 )使用堆排序

    class MaxHeap {
      map: Map<string, number> = new Map()
      heap: number[] = []
      init(str:string) {
        // 构建map字典
        const { map } = this;
        str.split('').forEach(item => {
          map.set(item, map.has(item) ? map.get(item) + 1 : 1);
        });
        this.heap = Array.from(map.values());
      }
      sort () {
        const iArr = this.heap;
        const n = iArr.length;
        if (n <= 1) return iArr;
        for (let i = Math.floor(n / 2); i >= 0; i--) {
          MaxHeap.maxHeapify(iArr, i, n);
        }
        for (let j = 0; j < n; j++) {
          MaxHeap.swap(iArr, 0, n - 1 - j);
          MaxHeap.maxHeapify(iArr, 0, n - 1 - j - 1);
        }
        return iArr;
      }
      // 排序并转成字符串
      sortToString () {
        const arr = this.sort(); // 这里对值进行排序
        const str = [];
        while (arr.length) {
          const top = arr.pop();
          for (const [k, v] of this.map) {
            // 值和值匹配
            if (v === top) {
              str.push(k.repeat(v));
              this.map.delete(k); // 使用过的key防止重复匹配 这里记得删除
              break
            }
          }
        }
        return str.join('');
      }
      // 交换两个元素
      static swap (arr, i, j) {
        if (i === j) return;
        [arr[i], arr[j]] = [arr[j], arr[i]];
      }
      // 构建最大堆的过程
      static maxHeapify (Arr, i, size) {
        // 左节点(索引)
        const l = (i << 1) + 1;
        // 右节点
        const r = (i << 1) + 2;
        let largest = i;
        // 父节点i和左节点l做比较取最大
        if (l <= size && Arr[l] > Arr[largest]) largest = l;
        // 右节点和最大值比较
        if (r <= size && Arr[r] > Arr[largest]) largest = r;
        if (largest !== i) {
          MaxHeap.swap(Arr, i, largest);
          MaxHeap.maxHeapify(Arr, largest, size);
        }
      }
    }
    
    function frequencySort(s: string): string {
      const mh = new MaxHeap();
      mh.init(s);
      return mh.sortToString();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 如果这个堆之前构建好,只需要少许修改,即可投入使用
    • 理解了最大堆的构建过程,这个还是比较推荐使用的
    • 需要注意的是在while和for的嵌套循环中的时间复杂度的考量
      • while是每次pop从n直到为0,因此是 n
      • for不会每次都执行n次,匹配到时会被break掉,因此是 logn
      • 所以整体时间复杂度为 O(nlogn)
  • 相关阅读:
    RHCE(第六天)
    10K起步的软件测试岗到底需要学什么?零基础进阶自动化测试需要哪些技术...
    SQL注入漏洞
    沉浸式视听体验:全景声技术是如何实现的?
    k8s、docker 卸载
    数据库的三大范式
    C++11补充:智能指针如std::unique_ptr如何添加自定义的deleter
    Python连接MYSQL、SQL Server、Oracle数据入库一网打尽
    CVE-2021-44548 Apache Solr 敏感信息泄露漏洞分析及复现
    ASEMI肖特基二极管MBR30200PT图片,MBR30200PT规格书
  • 原文地址:https://blog.csdn.net/Tyro_java/article/details/133564001