LeetCode 记录-791. 自定义字符串排序

93 阅读2分钟

LeetCode 记录-791. 自定义字符串排序

我的解法

思路

image.png

我的思路是计数。先用 order 生成一个字符-数组的 map 用来存 s 中出现的字符,同时在 map 中也添加了一个 "other" key,用来存 order 中不存在的字符。遍历一边 s,将 s 中的字符添加到对应的数组中。最后,拼接数组形成一个字符串并返回。

代码

/**
 * @param {string} order
 * @param {string} s
 * @return {string}
 */
var customSortString = function (order, s) {
  const orderMap = new Map([]);
  for (let c of order) {
    orderMap.set(c, []);
  }
  orderMap.set("other", []);

  for (let c of s) {
    if (orderMap.has(c)) {
      orderMap.get(c).push(c);
    } else {
      orderMap.get("other").push(c);
    }
  }
  return [...orderMap.values()].map((orderItem) => orderItem.join("")).join("");
};

复杂度分析(自我分析,不一定对)

时间复杂度

O(n+k)O(n+k),n 为 s 的长度,k 为 order 的长度。

空间复杂度

O(k+n)O(k+n),k 为 order 的长度,以 order 为基础建立 map 所需要的空间代价的期望是O(k)O(k)。n 为 s 的长度,因为在 map 每个 value 的数组中存了出现的字符,数组总的大小为 s 的长度。


官方解法 1: 自定义排序

思路

通过 order 生成一个权值表,然后用这个权值表对 s 进行排序。

权值表的生成思路如下:

遍历给定的字符串 order,将第一个出现的字符的权值赋值为 1,第二个出现的字符的权值赋值为 2,以此类推。所有未出现的字符的权值为 0。

代码

// 官方解法1: 自定义排序-理解思路后我写的解法
/**
 * @param {string} order
 * @param {string} s
 * @return {string}
 */
var customSortString = function (order, s) {
  const val = new Array(26).fill(0);
  for (let i = 0; i < order.length; i++) {
    val[order[i].charCodeAt() - "a".charCodeAt()] = i + 1;
  }

  const sArr = [...s];
  sArr.sort(
    (c0, c1) =>
      val[c0.charCodeAt() - "a".charCodeAt()] -
      val[c1.charCodeAt() - "a".charCodeAt()]
  );

  return sArr.join("");
};

// 官方解法1: 自定义排序-官方代码
/**
 * @param {string} order
 * @param {string} s
 * @return {string}
 */
var customSortString = function (order, s) {
  const val = new Array(26).fill(0);
  for (let i = 0; i < order.length; ++i) {
    val[order[i].charCodeAt() - "a".charCodeAt()] = i + 1;
  }
  const arr = new Array(s.length).fill(0).map((_, i) => s[i]);
  arr.sort(
    (c0, c1) =>
      val[c0.charCodeAt() - "a".charCodeAt()] -
      val[c1.charCodeAt() - "a".charCodeAt()]
  );
  let ans = "";
  for (let i = 0; i < s.length; ++i) {
    ans += arr[i];
  }
  return ans;
};

可以看到上面有两个函数,一个是我理解官方思路后自己写的,一个是直接 copy 的官方代码。有意思的是,它们逻辑看起来是差不多的(应该说是一模一样),但是最终的时间消耗差距蛮大,第一种我写的耗时 80ms,击败 9.57%。第二种则耗时 64ms,击败 60%。这两种写法区别在于从字符串生成数组以及将数组合并为字符串,官方的写法虽然看起来比较复杂,但是耗时很少。

复杂度分析

时间复杂度

O(nlogn+Σ)O(nlogn+∣Σ∣),其中 nn 是字符串 ss 的长度,ΣΣ 是字符集,在本题中 Σ=26∣Σ∣=26

  • 排序的时间复杂度为 O(nlogn)O(nlog⁡n)
  • 如果我们使用数组储存权值,数组的大小为O(Σ)O(∣Σ∣);如果我们使用哈希表储存权值,哈希表的大小与字符串 s 和 order 中出现的字符种类数相同,为叙述方便也可以记为 O(Σ)O(∣Σ∣)

空间复杂度

O(Σ)O(∣Σ∣),即为数组或者哈希表需要使用的空间。

官方解法 2: 计数排序

思路

和我的思路类似,就不详细介绍啦。

代码

var customSortString = function (order, s) {
  const freq = new Array(26).fill(0);
  for (let i = 0; i < s.length; ++i) {
    const ch = s[i];
    ++freq[ch.charCodeAt() - "a".charCodeAt()];
  }
  let ans = "";
  for (let i = 0; i < order.length; ++i) {
    const ch = order[i];
    while (freq[ch.charCodeAt() - "a".charCodeAt()] > 0) {
      ans += ch;
      freq[ch.charCodeAt() - "a".charCodeAt()]--;
    }
  }
  for (let i = 0; i < 26; ++i) {
    while (freq[i] > 0) {
      ans += String.fromCharCode(i + "a".charCodeAt());
      freq[i]--;
    }
  }
  return ans;
};

复杂度分析

时间复杂度

O(nlogn+Σ)O(nlogn+∣Σ∣),其中 nn 是字符串 ss 的长度,ΣΣ 是字符集,在本题中 Σ=26∣Σ∣=26

空间复杂度

O(Σ)O(∣Σ∣),即为数组或者哈希表需要使用的空间。