【算法-LeetCode】443. 压缩字符串(双指针;Array.splice())

443. 压缩字符串 - 力扣(LeetCode)

文章起笔:2021年11月6日21:22:32

问题描述及示例

给你一个字符数组 chars ,请使用下述算法压缩:

从一个空字符串 s 开始。对于 chars 中的每组 连续重复字符

如果这一组长度为 1 ,则将字符追加到 s 中。
否则,需要向 s 追加字符,后跟这一组的长度。
压缩后得到的字符串 s 不应该直接返回 ,需要转储到字符数组 chars 中。需要注意的是,如果组长度为 10 或 10 以上,则在 chars 数组中会被拆分为多个字符。

请在 修改完输入数组后 ,返回该数组的新长度。

你必须设计并实现一个只使用常量额外空间的算法来解决此问题。

示例 1:
输入:chars = [“a”,“a”,“b”,“b”,“c”,“c”,“c”]
输出:返回 6 ,输入数组的前 6 个字符应该是:[“a”,“2”,“b”,“2”,“c”,“3”]
解释:
“aa” 被 “a2” 替代。“bb” 被 “b2” 替代。“ccc” 被 “c3” 替代。

示例 2:
输入:chars = [“a”]
输出:返回 1 ,输入数组的前 1 个字符应该是:[“a”]
解释:
没有任何字符串被替代。

示例 3:
输入:chars = [“a”,“b”,“b”,“b”,“b”,“b”,“b”,“b”,“b”,“b”,“b”,“b”,“b”]
输出:返回 4 ,输入数组的前 4 个字符应该是:[“a”,“b”,“1”,“2”]。
解释:
由于字符 “a” 不重复,所以不会被压缩。“bbbbbbbbbbbb” 被 “b12” 替代。
注意每个数字在数组中都有它自己的位置。

提示:
1 <= chars.length <= 2000
chars[i] 可以是小写英文字母、大写英文字母、数字或符号

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/string-compression
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我的题解

一开始我是想用一个 Map 类型的变量(假设变量名为 map)来存储字符与其个数的映射关系。然后在遍历 chars 的过程中不断地累积相同字符的个数,但是写完之后我突然发现这种想法是错误的,因为题目中是只对 连续重复字符 进行压缩,而如果利用 Map 类型的变量来记录字符的数量的话,就会导致某些不连续的重复字符被错误累积。

比如输入:chars = ["a","a","b","b","a","a","a","c","c","c"],那么遍历完 chars 之后,在 map 中就会记录以下结果:

Map(3) {"a" => 5, "b" => 2, "c" => 3}
	[[Entries]]
	0: {"a" => 5}
	1: {"b" => 2}
	2: {"c" => 3}
size: 3

利用 entries() 变量进行遍历之后,就可以得到以下结果:

let arr = [...map.entries()];
arr
(3) [Array(2), Array(2), Array(2)]
	0: (2) ["a", 5]
	1: (2) ["b", 2]
	2: (2) ["c", 3]
length: 3

这显然不是我们预期的压缩效果,因为前面的两个连续 a 和后面的三个连续 a 是不连续的。

在这里插入图片描述

当相同字符不连续的情况

成功前的尝试

于是后来我改变了思路。我用两个指针分别指向某个连续重复字符子串的左右边界。其中 front 指向前边界,而 i 指针指向后边界。同时 i 指针也担当着遍历 chars 数组的任务。

在遍历 chars 的过程中,如果发现当前字符是某个连续重复字符子串的的后边界,就需要将当前的这个连续重复字符子串按照规则进行压缩,这里运用了关键的 splice() 这个数组方法来完成对原数组的改变。

这里一定要注意 splice() 函数会改变原数组,也就是说,在进行压缩操作后,可能会出现“下标塌陷”的现象。所以在使用 splice() 后一定要对 i 指针的位置做调整以手动改变遍历的节奏。

但是一开始我没有考虑清楚处理“下标塌陷”的逻辑,只是考虑到了重复字符的数量是一位数或两位数的情况,如果某个连续重复字符子串的字符数量超过两位就会出现问题。

// 我一开始处理下标缺陷的方案,没有考虑重复字符的位数超过两位的情况
i = i - front + 1 < 10 ? front + 1 : front + 2;


执行结果:解答错误
通过测试用例:71 / 72
输入:["a","a","a","a",...,"a","a","a","a","a","a"](这里有1000个连续的 "a")
输出:["a","1","0","0","2"]
预期结果:["a","1","0","0","0"]
时间:2021/11/06 21:27	

成功解决下标塌陷的问题

在浏览器里调试了一番后,我发现了上面的问题所在,所以修改了相应的处理“下标塌陷”的逻辑。

详解请看下方注释:

/**
 * @param {character[]} chars
 * @return {number}
 */
var compress = function(chars) {
  // front指针用于标识一个连续重复字符子串的前边界,其初始指向chars头部
  let front = 0;
  // 开始遍历chars数组
  for(let i = 0; i < chars.length; i++) {
    // 如果当前遍历字符和下一个待遍历字符是一样的,说明当前遍历字符不是目标子串的后边界
    if(chars[i] === chars[i + 1]) {
      // 跳过后面的逻辑
      continue;
    }
    // 如果发现当前遍历字符是某个子串的后边界,就要开始实现压缩逻辑了
    // 如果发现当前连续重复字符子串的长度为1,则只需要改变front的指向即可
    if(i - front === 0) {
      front = i + 1;
    } else if(i - front === 1) {
      // 如果发现当前连续重复字符子串的长度为2,则只需要将当前遍历字符改为'2',
      // 这样可以省去不必要的splice()操作,因为splice()操作比较耗费性能,能省则省
      chars[i] = '2';
    } else {
      // 如果发现当前连续重复字符子串的长度为超过2,则需要将计算当前连续重复字符子串的长度
      // 并且还要将计算所得的长度(数值类型)分割为一个个字符,
      let count = (i - front + 1).toString().split('');
      // 将上面计算所得的长度拼接到原子串的后面,注意这里使用了扩展运算符
      chars.splice(front + 1, i - front, ...count);
      // 这种情况下需要特别处理一下数组下标塌陷的问题
      i = front + count.length;
    }
    // 压缩完一个子串后,需要移动front指针的位置来为下一个子串的压缩做准备
    front = i + 1;
  }
  // 按照题目要求,最后还要返回被修改后的chars的数组的长度
  return chars.length;
};


提交记录
执行结果:通过
72 / 72 个通过测试用例
执行用时:72 ms, 在所有 JavaScript 提交中击败了90.72%的用户
内存消耗:39.6 MB, 在所有 JavaScript 提交中击败了73.53%的用户
时间:2021/11/06 21:30

在LeetCode的某些题目中,可能会要求直接在传入的参数上做修改,而不要特意返回某个值,这个时候就要注意不要对传入的参数做内存地址重新赋值的操作,那样的话可能虽然逻辑对了,但是就是不能通过提交。

官方题解

更新:2021年7月29日18:43:21

因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。

【更新结束】

更新:2021年11月6日21:27:27

参考:压缩字符串 - 压缩字符串 - 力扣(LeetCode)

【更新结束】

有关参考

暂无

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值