6014. 构造限制重复的字符串(贪心、优先队列)

380 阅读2分钟

「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」。

每日刷题第50天 2021.02.20

6014. 构造限制重复的字符串

题目

  • 给你一个字符串 s 和一个整数 repeatLimit ,用 s 中的字符构造一个新字符串 repeatLimitedString ,使任何字母 连续 出现的次数都不超过 repeatLimit 次。你不必使用 s 中的全部字符。

  • 返回 字典序最大的 repeatLimitedString 。

  • 如果在字符串 a 和 b 不同的第一个位置,字符串 a 中的字母在字母表中出现时间比字符串 b 对应的字母晚,则认为字符串 a 比字符串 b 字典序更大 。如果字符串中前 min(a.length, b.length) 个字符都相同,那么较长的字符串字典序更大。

示例

  • 示例1
输入:s = "cczazcc", repeatLimit = 3
输出:"zzcccac"
解释:使用 s 中的所有字符来构造 repeatLimitedString "zzcccac"。
字母 'a' 连续出现至多 1 次。
字母 'c' 连续出现至多 3 次。
字母 'z' 连续出现至多 2 次。
因此,没有字母连续出现超过 repeatLimit 次,字符串是一个有效的 repeatLimitedString 。
该字符串是字典序最大的 repeatLimitedString ,所以返回 "zzcccac" 。
注意,尽管 "zzcccca" 字典序更大,但字母 'c' 连续出现超过 3 次,所以它不是一个有效的 repeatLimitedString 。
  • 示例2
输入:s = "aababab", repeatLimit = 2
输出:"bbabaa"
解释:
使用 s 中的一些字符来构造 repeatLimitedString "bbabaa"。 
字母 'a' 连续出现至多 2 次。 
字母 'b' 连续出现至多 2 次。 
因此,没有字母连续出现超过 repeatLimit 次,字符串是一个有效的 repeatLimitedString 。 
该字符串是字典序最大的 repeatLimitedString ,所以返回 "bbabaa" 。 
注意,尽管 "bbabaaa" 字典序更大,但字母 'a' 连续出现超过 2 次,所以它不是一个有效的 repeatLimitedString 。

提示

  • 1 <= repeatLimit <= s.length <= 105
  • s 由小写英文字母组成

解法

解题思路

  • 贪心的思路
  • 记录几个方法:'a'.charCodeAt()和String.fromCharCode()方法
    • 'a'.charCodeAt(),将字母转换为字典序数值
    • String.fromCharCode(value)将值转换为字母
    • map集合:map.set(s[i], (map.get(s[i]) || 0) + 1)
    • 数组的解构:let arr = [...map.keys()].sort().reverse()
  • 总结: 使用数组记录的不好的地方,就是其中会包含很多0的情况,易出错。因此可以转化思路,用map集合记录,而后将map集合转换为数组,再进行排序。
  • 具体来说:
    1. 字典序大的字符数有剩余,且结果字符串结尾不为此字符的就加上此字符的最小重复数,字符;
    2. 字典序大的字符数有剩余,且结果字符串结尾为此字符的就加上后面字符的一个隔开;
    • 直到字符用完或唯一能用的字符为结果字符结尾的字符。

延伸(快慢指针)

  1. 字典序大的字符数有剩余,且结果字符串结尾不为此字符的就加上此字符的最小重复数,字符;
  2. 字典序大的字符数有剩余,且结果字符串结尾为此字符的就加上后面字符的一个隔开;
  • 直到字符用完或唯一能用的字符为结果字符结尾的字符
/**
 * @param {string} s
 * @param {number} repeatLimit
 * @return {string}
 */
var repeatLimitedString = function(s, repeatLimit) {
  let len = s.length;
  if(len == 1) return s;
  s = s.split('');
  
  let compare = function (template) {
    return template.sort((a,b) => b[0] - a[0]);
  }
  
  let charArr = new Array(26).fill(0);
  let a = 'a'.charCodeAt();

  // let indexArr = new Array(26);
  for(let i = 0; i < len; i++) {
    let tempt = s[i].charCodeAt() - a;
    charArr[tempt]++;
  }
  // 预处理数据
  // console.log(charArr);
  
  let index = charArr.length - 1;
  let ans = '';
  let next;
  let flag = false;
  while(true){
    // while(charArr[index] != 0){
      // 找到当前字典序最大,且不为0
      if(charArr[index] <= repeatLimit){
        for(let j = 0; j < charArr[index]; j++){
          ans += String.fromCharCode(index + a); 
        }
        charArr[index] = 0;
        flag = true;
      }else if(charArr[index] > repeatLimit){
        for(let j = 0; j < repeatLimit; j++){
          ans += String.fromCharCode(index + a); 
        }
        charArr[index] -= repeatLimit;
        if(index == 0) return ans;
        next = index - 1;
        while(charArr[next] == 0){
          next--;
          if(next == -1) return ans;
        }
        ans += String.fromCharCode(next + a);
        charArr[next]--;
      }
      // 确保下一次遍历的是有长度的
      // 需不需要找下一个
      if(charArr[index] == 0) {
        // 当前最大的已经排完,需要找次小的
        // 需要找到一个不为0的长度
        // 如果是直接剪完的,就不存在next
        if(flag) {
          while(charArr[index] == 0){
            index--;
            if(index == -1) return ans;
          }
          // console.log(index);
        }else {
          while(charArr[next] == 0){
            next--;
            if(next == -1) return ans;
          }
          index = next;
        }
      }
    // }
  }
};