力扣经典题---压缩字符串(JS实现)

题目描述:

给你一个字符数组 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"]
解释:唯一的组是“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] 可以是小写英文字母、大写英文字母、数字或符号

思路:

  1. 初始化索引:定义两个索引,readIndex用于遍历原始字符数组charswriteIndex用于记录压缩后数据在数组中的位置。

  2. 遍历数组:使用while循环,通过readIndex从数组的开始位置遍历到结束。

  3. 记录当前字符和计数:在循环内部,使用currentChar记录当前遍历到的字符,count记录该字符连续出现的次数。

  4. 计算连续字符数:使用内部的while循环来确定currentChar连续出现的次数,同时更新readIndex以跳过这些连续的字符。

  5. 压缩逻辑

    • 如果count大于1,表示有多个连续的相同字符,需要压缩。此时,将currentChar写入chars数组的writeIndex位置,然后递增writeIndex
    • count转换为字符串countStr,然后遍历countStr的每个字符,将它们依次写入chars数组,每次写入后递增writeIndex
    • 如果count等于1,表示没有压缩的必要,直接将currentChar写入chars数组的writeIndex位置,然后递增writeIndex
  6. 移动读取指针:在每次内部循环结束后,将readIndex向前移动一位,以便在下一次迭代中读取下一个字符。

  7. 返回新长度:当readIndex遍历完整个数组后,循环结束。此时,writeIndex表示压缩后的数据在数组中的长度。返回writeIndex作为函数的结果。

代码实现:

/**
 * @param {character[]} chars
 * @return {number}
 */
var compress = function (chars) {
    let readIndex = 0; // 用于遍历原始数组
    let writeIndex = 0; // 用于记录压缩后字符串在数组中的位置

    while (readIndex < chars.length) {
        let count = 1; // 记录当前字符的连续出现次数
        let currentChar = chars[readIndex];

        // 计算当前字符连续出现的次数
        //当前指针+1小于数组长即没有越界时&&当前元素和下一个元素相同
        //也可以用当读指针 read 位于字符串的末尾,或读指针 read 指向的字符不同于下一个字符时来结束循环
        //readIndex === length - 1 || chars[readIndex] !== chars[readIndex + 1]
        while (readIndex + 1 < chars.length && chars[readIndex + 1] === currentChar) {
            readIndex++;
            count++;
        }

        // 如果当前字符连续出现次数大于1,则需要记录字符和次数
        if (count > 1) {
            // 将字符写入数组
            chars[writeIndex] = currentChar;
            writeIndex++;

            // 将次数转换为字符串,并根据需要拆分写入数组
            let countStr = count.toString();
            for (let i = 0; i < countStr.length; i++) {
                chars[writeIndex] = countStr[i];
                writeIndex++;
            }
        } else {
            // 如果连续出现次数为1,直接将字符写入数组
            chars[writeIndex] = currentChar;
            writeIndex++;
        }

        // 移动读取指针
        readIndex++;
    }

    // 返回压缩后数组的新长度
    return writeIndex;
};

 运行结果分析

运行后发现内存的消耗比较高

上述代码的效率主要取决于字符数组的遍历次数和写入次数。由于题目要求使用常量额外空间,我们不能使用额外的数据结构来存储中间结果。因此优化主要集中在减少不必要的操作和优化写入逻辑上。

性能优化:

在查询资料后,发现要想优化性能可以从以下几个方面进行

  1. 减少字符到字符串的转换次数:在处理重复字符组的计数时,如果计数大于9,则需要将其转换为字符串并逐个字符写入数组。我们可以预先计算出需要写入的字符数,然后一次性写入,而不是在循环中重复转换和写入。

  2. 优化写入逻辑:在写入字符和计数时,我们可以预先分配好空间,然后一次性写入,减少数组的多次修改。

  3. 减少条件判断:尽量减少在循环中的条件判断,尤其是在内层循环中。

在查询资料后,发现要想优化性能可以从以下几个方面进行

优化实现思路:

  1. 初始化指针writeIndex初始化为0,用于记录压缩后字符串在数组中的位置。

  2. 遍历字符数组:使用一个for循环遍历chars数组,其中i作为当前遍历到的位置的索引。

  3. 记录当前字符:在循环内部,currentChar变量用来存储当前遍历到的字符。

  4. 计算连续字符数:使用一个while循环来计算当前字符连续出现的次数。如果下一个字符与当前字符相同,count加1,并且i向前移动一位。

  5. 写入当前字符:无论连续字符数是1还是更多,当前字符都会被写入到chars数组的writeIndex位置,然后writeIndex递增。

  6. 写入连续字符数:如果count大于1,表示有连续的字符需要压缩。此时,将count转换为字符串countStr,然后使用另一个for循环将countStr中的每个字符写入到chars数组中,每次写入后writeIndex递增。

  7. 更新索引:在while循环中,由于已经确定了连续字符的数量,所以i索引会跳过这些连续的字符,直接移动到下一个不同的字符处。

  8. 返回新长度:遍历完成后,writeIndex表示压缩后的字符串在数组中所占的最终位置,即新的长度。函数返回这个值。

 优化代码实现:

/**
 * @param {character[]} chars
 * @return {number}
 */
var compress = function (chars) {
    let writeIndex = 0; // 用于记录压缩后字符串在数组中的位置


    for (let i = 0; i < chars.length; i++) {
        let currentChar = chars[i];
        let count = 1; // 记录当前字符的连续出现次数

        // 计算当前字符连续出现的次数
        //当前指针+1小于数组长即没有越界时&&当前元素和下一个元素相同
        //也可以用当读指针 read 位于字符串的末尾,或读指针 read 指向的字符不同于下一个字符时来结束循环
        //readIndex === length - 1 || chars[readIndex] !== chars[readIndex + 1]
        while (i + 1 < chars.length && chars[i + 1] === currentChar) {
            count++;

        //当前已遍历元素的下标
            i++;

        }

        // 写入字符
        chars[writeIndex++] = currentChar;
       // console.log(chars[writeIndex++])

        // 如果连续出现次数大于1,需要写入次数
        if (count > 1) {
            // 将次数转换为字符串并逐个字符写入数组
            let countStr = count.toString();
            for (let j = 0; j < countStr.length; j++) {  
                chars[writeIndex++] = countStr[j];
            }
        }
    }

    // 返回压缩后数组的新长度
    return writeIndex;
};

<think>好的,我需要帮助用户找到在JavaScript实现字符串压缩的方法。根据用户提供的引用内容,看起来用户已经了解了一些基本的字符串压缩算法,比如将连续重复字符替换为字符加次数的方式,例如aabcccccaaa变成a2b1c5a3。用户可能想知道如何自己实现这样的算法,或者是否有现成的库可以使用。 首先,我需要回顾用户提供的引用内容。引用1、2、3都提到了相同的基本算法:遍历字符串,统计连续字符的出现次数,然后生成压缩后的字符串。如果压缩后的字符串没有变短,则返回原字符串。引用4提到需要将结果转储到字符数组,并且处理长度超过10的情况,但用户的问可能更关注基本实现,所以可能暂时不需要处理这个细节。 用户的问有两个部分:如何自己实现算法,以及如何使用相关库。引用3提供了一个JavaScript实现示例,可以作为参考。我需要先解析这个示例代码,理解其逻辑,然后解释给用户。同时,用户可能想知道是否有现有的库可以使用,比如lz-string,这可能需要我补充相关知识,因为提供的引用中没有提到库的部分。 在实现算法部分,我需要逐步解释代码的逻辑,比如初始化变量、遍历字符串、统计连续字符、拼接结果,以及最后比较长度。同时,需要注意边界情况,比如空字符串或所有字符都不连续的情况。 关于使用库,我需要确认是否有常用的JavaScript库用于字符串压缩,比如lz-string,并简单介绍其用法和适用场景,比如用于本地存储压缩数据。需要提醒用户库的适用性,比如如果是为了特定传输需求,可能需要不同的压缩算法。 可能还需要比较自定义算法和库的优缺点。自定义算法简单但压缩率低,适合特定情况(如连续重复字符多的情况),而库可能提供更高的压缩率但增加依赖。 最后,生成相关问,比如如何优化自定义算法,处理非连续字符,或者比较不同压缩库的性能等。</think>### 如何在JavaScript实现字符串压缩 #### 方法1:基础算法实现(针对连续重复字符) **核心思路**:遍历字符串,统计连续字符的出现次数,生成`字符+次数`格式的新字符串。若压缩后未变短,返回原字符串。 **实现步骤**: 1. **初始化变量**:记录当前字符`currentChar`、计数器`count`和结果字符串`compressedStr`。 2. **遍历字符串**:从第二个字符开始比较: ```javascript function compressString(S) { if (S.length === 0) return S; let compressedStr = ''; let currentChar = S[0]; let count = 1; for (let i = 1; i < S.length; i++) { if (S[i] === currentChar) { count++; } else { compressedStr += currentChar + count; // 拼接结果 currentChar = S[i]; count = 1; } } compressedStr += currentChar + count; // 处理末尾字符 return compressedStr.length < S.length ? compressedStr : S; } ``` 3. **边界处理**:空字符串直接返回;遍历结束后需拼接最后一个字符的统计结果。 4. **压缩有效性判断**:比较压缩前后长度决定返回值[^3]。 **示例**: - 输入`aabcccccaaa` → 输出`a2b1c5a3` - 输入`abc` → 返回原字符串压缩后`a1b1c1`长度相同) --- #### 方法2:使用第三方库(如`lz-string`) **适用场景**:需要更高压缩率或处理非连续重复数据时。 **实现步骤**: 1. **安装库**: ```bash npm install lz-string ``` 2. **压缩与解压**: ```javascript import LZString from 'lz-string'; // 压缩 const compressed = LZString.compress("原始字符串"); // 解压 const decompressed = LZString.decompress(compressed); ``` 3. **特性**: - 支持多种压缩算法(如Base64、UTF16) - 适用于本地存储(LocalStorage)空间优化[^4] --- #### 两种方法对比 | 对比项 | 基础算法 | lz-string库 | |--------------|-----------------------------------|------------------------------| | **压缩率** | 仅针对连续重复字符(场景有限) | 通用压缩,支持复杂数据 | | **复杂度** | 时间复杂度$O(n)$,空间$O(n)$ | 依赖库实现,通常更高效率 | | **适用性** | 简单场景(如日志中有大量重复字符)| 需高压缩率或非重复结构数据 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值