leetcode刷题(javaScript)——字符串相关场景题总结

js中字符操作方法

在JavaScript中,字符串(String)是一种基本的数据类型,也是一种不可变的字符序列。字符串有许多内置的方法,可以进行各种常见的操作。以下是在LeetCode刷题中可能会遇到的字符串相关操作:

字符串查询

  • charAt(index): 返回指定位置的字符。
  • charCodeAt(index): 返回在指定的位置的字符的 Unicode 编码。
  • indexOf(substring): 返回子字符串在字符串中首次出现的位置。
  • lastIndexOf(substring): 返回子字符串在字符串中最后出现的位置。
  • includes(substring): 判断字符串是否包含指定的子字符串。

字符串截取

  • substring(startIndex, endIndex): 返回字符串中介于两个指定下标之间的字符。
  • slice(startIndex, endIndex): 提取字符串的某个部分,并返回一个新的字符串。
  • substr(startIndex, length): 返回从指定位置开始的指定长度的子字符串。

字符串修改

  • replace(searchFor, replaceWith): 在字符串中用一些字符替换另一些字符。
  • toUpperCase(): 将字符串转换为大写。
  • toLowerCase(): 将字符串转换为小写。
  • trim(): 从字符串的两端删除空白字符。

字符串分割

  • split(separator, limit): 通过分隔符将字符串分割成子字符串数组。

字符串连接

  • concat(str1, str2, ...): 连接两个或多个字符串,并返回新的字符串。
  • 使用 + 运算符也可以连接字符串。

其他操作

  • repeat(count): 返回指定次数重复的字符串。
  • startsWith(substring): 判断字符串是否以指定的子字符串开头。
  • endsWith(substring): 判断字符串是否以指定的子字符串结尾。
  • padStart(targetLength, padString): 在字符串的开头填充指定的字符,直到达到指定的长度。
  • padEnd(targetLength, padString): 在字符串的末尾填充指定的字符,直到达到指定的长度。

使用正则表达式

JavaScript中的字符串与正则表达式(RegExp)紧密集成,可以进行复杂的模式匹配和搜索操作,如:

  • match(regexp): 返回一个数组,包含匹配的结果。
  • search(regexp): 返回第一个与正则表达式匹配的子串的起始位置。
  • replace(regexp, replaceWith): 使用正则表达式进行搜索和替换。

在刷题时,这些字符串操作方法可以帮助你轻松处理字符串问题,如字符串的查找、替换、分割、连接等常见场景。熟悉这些方法对于解题至关重要。

字符串方法使用

58. 最后一个单词的长度

给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。

单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。

 思路:首先想到按空格分隔字符串,调用split方法,分隔方式是空格' ';

其次,考虑尾部可能有多余的空格,因此在分割字符串前需要用trim方法去掉首尾空格。

最后返回字符数组的最后一项的长度。可以用数组的pop方法返回字符串,用.length方法获取字符串的长度

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLastWord = function (s) {
    let str = s.trim().split(' ');//去首尾空格,并用空格分开为数组
    return str.pop().length;//返回数组最后一项字符串的长度
};

 14. 最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

思路:以第一个字符串为标准,尝试取前缀进行后面的字符比较。在比较过程中如果发现有字符小于这个前缀可提前结束比较。通过slice复制前缀字符串,通过index显示记录前缀遍历的下标。如果比较过程中有字符与前缀不等,返回之前存储的pre前缀信息。一趟for循环遍历后,如果都相等,将subStr更新为pre。

/**
 * @param {string[]} strs
 * @return {string}
 */
var longestCommonPrefix = function (strs) {
    let pre = '';
    let subStr = '';
    let index = 1;
    while (index <= strs[0].length) {
        let subStr = strs[0].slice(0, index);
        for (let i = 1; i < strs.length; i++) {
            if (strs[i].length < index) {//某个字符串的长度小于了前缀的长度,返回前缀
                return pre;
            }
            if (strs[i].slice(0, index) != subStr) {
                return pre;
            }
        }
        pre = subStr;
        index++;
    }
    return pre;
};

 151. 反转字符串中的单词

给你一个字符串 s ,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

思路:最简单的方式就是调用js的字符串方法处理。现用正则匹配所有多余的空格,多余的只保留一个空格。将整个字符串按照空格转成字符串数组。对数组进行翻转,翻转后的数组拼接为字符串。

/**
 * @param {string} s
 * @return {string}
 */
var reverseWords = function (s) {
    s = s.trim().replace(/\s+/g, ' ');//去除多余空格
    s = s.split(' ').reverse().join(' ');//转成数组,用数组的reverse翻转整个字符串数组,在拼成字符串返回
    return s;
};

在正则表达式中,/\s+/g这个表达式可以分解成以下几部分:

  1. /: 正则表达式的开始和结束标志,表示正则表达式的开始和结束。
  2. \s: 表示匹配任意空白字符,包括空格、制表符、换行符等。
  3. +: 表示匹配前面的元素一次或多次,即匹配一个或多个空白字符。
  4. g: 表示全局匹配模式,即在整个字符串中查找所有匹配的内容,而不是在找到第一个匹配后就停止。

空间复杂度分析:s.trim()trim()方法会创建一个新的字符串,去除原始字符串首尾的空格,因此空间复杂度为O(n);s.replace(/\s+/g, ' ')replace()方法会创建一个新的字符串,用于替换多余的空格为单个空格,因此空间复杂度为O(n);s.split(' ')split()方法会创建一个新的数组,将字符串按空格分割成单词数组,空间复杂度为O(n)。

总的空间复杂度为O(n)

进阶:如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 O(1) 额外空间复杂度的 原地 解法。

整体思路:考虑原地求解,现将数组用双指针原地翻转;然后,考虑去除多余空格;然后翻转每个单词。

由于字符串s本身可能有多个空格,操作字符串后字符串长度可能小于原始长度。如何返回操作后的字符串呢?

利用双指针,慢指针存储翻转后取出多余空格的字符,快指针向右遍历,右指针结束的时候,此时慢指针指的位置就是最终的字符串最后一项。

此时,直接将字符串的 length 属性设置为新的长度,这样就相当于在原数组的基础上截断了字符串。这种方法并不会创建新的字符串,因此空间复杂度为常数。

在JavaScript中,字符串是不可变的,所以你不能直接在原数组上修改字符串。但是,如果你有一个字符数组,你可以进行原地翻转字符串。这样其实空间复杂度还是o(n),所以在js中,其实没有更好的方案,以下是参考

/**
 * @param {string} s
 * @return {string}
 */
var reverseWords = function (s) {
    // 字符串转数组
    s = Array.from(s);
    reverse(s, 0, s.length - 1); //翻转整个字符串
    removeSpace(s); //原地删除多余空格
    let start = 0;
    for (let i = 0; i < s.length; i++) {
        if (s[i] === " ") {
            reverse(s, start, i - 1);
            start = i + 1;
        }
    }
    reverse(s, start, s.length - 1);
    return s.join("");
};
// 辅助函数:反转字符串中指定范围的字符
function reverse(s, start, end) {
    while (start < end) {
        let temp = s[start];
        s[start] = s[end];
        s[end] = temp;
        start++;
        end--;
    }
    return s;
}
function removeSpace(s) {
    let slow = 0,
        quick = 0;
    while (quick < s.length) {
        if (s[quick] != " ") {
            s[slow++] = s[quick];
        } else if (slow != 0 && quick < s.length - 1 && s[quick + 1] != " ") {
            //s[quick]=' '但是后面不等于空格
            s[slow++] = s[quick];
        }
        quick++;
    }
    s.length = slow;
}

滑动窗口

438. 找到字符串中所有字母异位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

 思路:字符串比数组麻烦多了。这个题利用p.length长度的滑动窗口,对窗口内的字符串与p字符串进行比较,如果相等就将滑动窗口左边的下标记录在结果数组中。但是这个题对时间复杂度有限制,会导致有些用例超时,所以在比较两个字符串是否相等的时候最好不要用sort排序。其实可以用滑动窗口的动态维护一个存放字符频率的数组numsCur。利用字符出现的频率和p数组字符频率进行比较,如果有一个不相等就为false。并且numsCur每次只会删除和新增一次。这里题目都是小写字母,用字母的编码-小写字符a的编码就能将a-z映射到0-25个数组下标了。

/**
 * @param {string} s
 * @param {string} p
 * @return {number[]}
 */
var findAnagrams = function (s, p) {
    if (s.length < p.length) return [];
    let res = [];
    let numsP = Array(26).fill(0);//统计每个字符出现的频率
    let numsCur = Array(26).fill(0);
    let left = 0; right = p.length - 1;
    for (let i = 0; i < p.length; i++) {//初始化P,cur数组
        let indexP = p[i].charCodeAt(0) - 'a'.charCodeAt(0);
        numsP[indexP]++;
        let indexCur = s[i].charCodeAt(0) - 'a'.charCodeAt(0);
        numsCur[indexCur]++;
    }
    //判断第一个p.length子串是否相等
    if (isSame(numsCur, numsP)) {
        res.push(left);
    }
    while (right < s.length - 1) {
        //减少left对应字符的出现频率,增加right的字符出现频率
        numsCur[s[left].charCodeAt(0) - 'a'.charCodeAt(0)]--;
        left++;
        right++;
        numsCur[s[right].charCodeAt(0) - 'a'.charCodeAt(0)]++;
        if (isSame(numsCur, numsP)) {
            res.push(left);
        }
    }
    return res;

};

// 检查两个字符数组是否为异位词
function isSame(num1, num2) {
    for (let i = 0; i < 26; i++) {
        if (num1[i] !== num2[i]) {
            return false;
        }
    }
    return true;
}

76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

思路:字符串的一道困难题。思路不难,边界条件比较多。从思考到写debugg一个小时完成。思路就是利用左右指针构建一个滑动窗口,left的边界值是s.length-t.length。通过一个mapT存储t中元素出现的频率,同样维护一个mapS,记录在t中有key的元素以及频率。在left向右和right向右过程中,动态更新mapS。判断mapT和mapS是否可以相等。

如果mapT和mapS大小相等,所有mapS已经有了mapT中所有元素,此时再看频率,如果mapS有元素频率小于mapT中相应元素的频率,返回fasle。

如何更新left,如果滑动窗口里已经找到了相等的串,移动左指针,尝试看是否有更小的串。

可以看提示给的信息,更容易理解:

/**
 * @param {string} s
 * @param {string} t
 * @return {string}
 */
var minWindow = function (s, t) {
  if (s.length < t.length) return "";
  let mapT = new Map();
  let mapS = new Map();
  let minStr = "";
  let left = 0,
    right = t.length - 1; //设置左指针的起始位置
  //初始化mapT
  for (let i = 0; i < t.length; i++) {
    mapT.set(t[i], mapT.has(t[i]) ? mapT.get(t[i]) + 1 : 1); //设置字符出现的频率
  }
  //初始化mapS,只记录与mapT相同的key
  for (let i = left; i <= right; i++) {
    if (mapT.has(s[i])) {
      //如果T有这个字符
      mapS.set(s[i], mapS.has(s[i]) ? mapS.get(s[i]) + 1 : 1);
    }
  }
  while (right < s.length && left <= s.length - t.length) {
    if (compareTwoMap(mapT, mapS)) {
      //判断窗口map是否相等
      const curStr = s.slice(left, right + 1);
      if (minStr.length) {
        if (minStr.length > curStr.length) {
          minStr = curStr;
        }
      } else {
        minStr = curStr;
      }
      //如果相等,尝试移动左指针,更新map缩小返回,看是否相等
      if (mapS.has(s[left])) {
        const count = mapS.get(s[left]) - 1;
        if (count == 0) {
          mapS.delete(s[left]);
        } else {
          mapS.set(s[left], count);
        }
      }
      left++;
    } else {
      //不相等,右指针扩大范围,更新map
      right++;
      if (mapT.has(s[right])) {
        //如果T有这个字符
        mapS.set(s[right], mapS.has(s[right]) ? mapS.get(s[right]) + 1 : 1);
      }
    }
  }
  return minStr;
};
function compareTwoMap(mapT, mapS) {
  //比较两个map是否相等
  if (mapT.size != mapS.size) {
    return false;
  } else {
    //size相等,说明字符都出现了,看频率是否相等
    //比较相同key的val是否相等
    for (let key of mapT.keys()) {
      if (mapT.get(key) > mapS.get(key)) {
        //s数量如果小的话
        return false;
      }
    }
  }
  return true;
}

 

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三月的一天

你的鼓励将是我前进的动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值