字符串算法一般解题指南(前端JS)

本文主要针对 leetcode 中部分字符串相关的简单、中等算法题进行总结分析,提供几个易于理解,便于记忆的解题范式,希望可以帮到大家。

其中涉及到的字符串方法,总结在前面,如有不熟悉,建议先看一遍这篇文章:JavaScript 字符串方法总结

查找: indexOf(字符,索引起始)、search(字符)、charAt(索引), includes()

截取:slice[start, end)、substring[start, end)、substr(start, length)

其他:split()、trim()、concat()、reverse()

Set:has()、add()、delete()

回文字符串:正着读和倒着读完全一样

js 复制代码
function isPalindrome(str){
   // 方法一
	let newStr = str.split('').reverse().join('') 
  if(newStr === str) return true
  return false
  
  // 方法二
  for(let i = 0; i < str.length/2; i++){
  	if(str[i] !== str[length - 1 - i]) return false
  }
  return true
}

1 无重复字符的最长子串

题目: 给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度

思路: 无重复考虑 Set,遍历字符串,依次添加到 Set 中,当添加失败时说明出现重复字符。要求连续子串,考虑滑动窗口式的快慢指针遍历,解法如下:

1.初始化一个Set记录字符是否重复

2.初始化快慢指针,慢指针遍历字符,快指针循环右移,并将字符 add 到Set中,直至出现重复字符

3.此时Set.size即该次遍历下最长不重复子串,更新res

4.自第二次遍历起,Set中不断删掉前面遍历过的字符,直到与快指针所在元素不重复后,重新开始 add

js 复制代码
/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    const mySet = new Set();
    let res = 0;
    let fast = 0
    for(let i = 0; i < s.length; i++){
        if(i !== 0){
            mySet.delete(s.charAt(i-1))
        }
        while(fast < s.length && !mySet.has(s[fast])){
            mySet.add(s[fast])
            fast++
        }
        res = Math.max(res, mySet.size)
    }
    return res
};

2 字符串相加

题目: 给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。

思路: 逆序遍历两个字符串,对位字符转为整形后相加

1.考虑进位carry

2.逆序遍历两个字符串,循环遍历条件为len1 >= 0 || len2 >= 0 || carry != 0

3.当某字符串先遍历完成,缺位补0进行相加const x = len1 >= 0 ? num1.charAt(len1--) - 0 : 0

4.对位字符转数字后求和,取余添加到res,取整作为进位carry

js 复制代码
/**
 * @param {string} num1
 * @param {string} num2
 * @return {string}
 */
var addStrings = function(num1, num2) {
    let len1=num1.length - 1
    let len2=num2.length - 1
    let carry = 0
    const res = []
    while(len1 >=0 || len2 >=0 || carry !== 0){
        const x = len1 >= 0 ? num1.charAt(len1) - 0 : 0
        const y = len2 >= 0 ? num2.charAt(len2) - 0 : 0
        const sum = x + y + carry
        const ele = sum % 10
        carry = Math.floor(sum / 10)
        res.unshift(ele)
        len1--
        len2--
    }
    return res.join('')
};

3 比较版本号

题目: 给你两个版本号 version1 和 version2 ,请你比较它们。版本号由一个或多个修订号组成,各修订号由一个 '.' 连接。每个修订号由多位数字组成,可能包含前导零 。 返回规则如下:

  • 如果 version1 > version2 返回 1,
  • 如果 version1 < version2 返回 -1,
  • 除此之外返回 0。

思路: 通过"."将字符串分割为数组,依次将字符转成数值,进行比较大小

1.版本号分割成数组

2.遍历两个数组,将字符转数值然后比较

3.两个数组长度不一致情况,用0补位

js 复制代码
/**
 * @param {string} version1
 * @param {string} version2
 * @return {number}
 */
var compareVersion = function(version1, version2) {
    const arr1 = version1.split('.')
    const arr2 = version2.split('.')
    let len1 = arr1.length
    let len2 = arr2.length
    let max = Math.max(len1, len2)
    for(let i = 0; i < max; i++){
        const num1 = i >= len1 ? 0 : Number(arr1[i])
        const num2 = i >= len2 ? 0 : Number(arr2[i])
        if(num1 >num2){
            return 1
        }else if(num1 < num2){
            return -1
        }
    }
    return 0
};

4 有效的括号

题目: 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。

思路: 遇到左括号存起来,遇到右括号即进行匹配

1.新建一个Map,设置左右括号为键值对;新建一个栈,存储左括号

2.遍历字符串,遇到左括号,存到栈中

3.遇到右括号,查询此时栈顶元素是否匹配

js 复制代码
/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
    const myMap = new Map([[')','('],[']','['],['}','{']])
    const arr = ['(','[','{']
    const stack = []
    for(let i = 0; i < s.length; i++){
        if(arr.includes(s[i])){
            stack.push(s[i])
        }else{
            if(stack.pop() !== myMap.get(s[i])){
                return false
            }
        }
    }
    return !stack.length
};

5 最长回文子串

题目: 给你一个字符串 s,找到 s 中最长的回文子串。

思路: 从中间向两边循环确认字符是否相等

1.遍历字符串,每个字符都可能是回文子串的中心

2.通过左右指针不断分离确定最长回文子串

3.考虑回文子串两种长度情况:奇数、偶数

js 复制代码
/**
 * @param {string} s
 * @return {string}
 */
var longestPalindrome = function(s) {
    let res = ''
    for(let i = 0; i < s.length; i++){
        let r = i
        let l = i+1
        while(r >= 0 && l < s.length && s[r] === s[l]){
            r--
            l++
        }
        res = res.length > l-r-1 ? res : s.slice(r+1, l)
        let start = end = i
        while(start >= 0 && end < s.length && s[start] === s[end]){
            start--
            end++
        }
        res = res.length > end-start-1 ? res : s.slice(start+1, end)
    }
    return res
};

6 括号生成

题目: 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且有效的括号组合。

思路:n-1 推导出 n 的组合情况,只需要遍历 n-1 的所有组合,在所有组合的每个位置填入一对括号()并去重即可。

1.新建Set,存储n=1时的有效括号组合

2.从n=2开始遍历,考虑Set中每个元素增加1对括号的情况

3.更新Set

js 复制代码
/**
 * @param {number} n
 * @return {string[]}
 */
var generateParenthesis = function(n) {
    let res = new Set(["()"])
    for(let i = 2; i <= n; i++){
        const mySet = new Set()
        res.forEach(item => {
            for(let j = 0; j < item.length; j++){
                mySet.add(item.slice(0,j) + '()' + item.slice(j))
            }
        })
        res = mySet
    }
    return [...res]
};

7 字符串相乘

题目: 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。

思路: 遍历两个字符串,将对位字符转为数值后相乘保存到数组中,最后拼接为字符串

1.新建一个全为0数组res,长度等于两个字符串长度之和

2.嵌套逆序遍历两个字符串,num1[i]num2[j]的乘积是res[i+j](取整累加)和res[i+j+1](取余赋值)

3.将res前置0清空

js 复制代码
/**
 * @param {string} num1
 * @param {string} num2
 * @return {string}
 */
var multiply = function(num1, num2) {
    let len1 = num1.length
    let len2 = num2.length
    const res  = new Array(len1+len2).fill(0)
    for(let i = len1-1; i >= 0; i--){
        for(let j = len2-1; j >= 0; j--){
            const result = num1[i] * num2[j] + res[i+j+1]
            res[i+j] += Math.floor(result / 10)
            res[i+j+1] = result % 10
        }
    }
    let p = 0
    while(res.length > 1 && res[p] === 0){
        res.shift()
    }
    return res.join('')
};

8 最多删除一个字符得到回文

题目: 给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串

思路: 当字符串不回文时,删除左边或者右边

1.封装回文子串判断方法,传参(str, l ,r)

2.初始化两个指针,一个指向字符串头部,另一个指向尾部

3.循环判断两个指针所指的字符是否相等

  • 相等,左指针右移,右指针左移
  • 不相等,分别判断左指针右移或者右指针左移情况下,是否回文

4.如果两个指针所指的字符串不等那么不对称发生了,意味着这是一个可以"删掉试试看"的操作点

js 复制代码
/**
 * @param {string} s
 * @return {boolean}
 */
var validPalindrome = function(s) {
    let start = 0
    let end = s.length - 1
    while(start < end && s[start] === s[end]){
        start++
        end--
    }
    if(start < end){
        if(isPadlindrome(s, start+1,end) || isPadlindrome(s, start, end-1)) return true
        return false
    }
    return true
};

const isPadlindrome = (str, l, r) => {
    while(l < r){
        if(str[l] !== str[r]) return false
        l++
        r--
    }
    return true
}

9 字符串匹配问题

题目:

设计一个支持以下两种操作的数据结构:

arduino 复制代码
void addWord(word)
bool search(word)

search(word) 可以搜索文字或正则表达式字符串,字符串只包含字母 . 或 a-z 。. 可以表示任何一个字母。

思路:

1.这道题要求字符串既可以被添加、又可以被搜索,这就意味着字符串在添加时一定要被存在某处。键值对存储,我们用 Map(或对象字面量来模拟 Map)。

2.注意,这里为了降低查找时的复杂度,我们可以考虑以字符串的长度为 key,相同长度的字符串存在一个数组中,这样可以提高我们后续定位的效率。

3.难点在于 search 这个 API,它既可以搜索文字,又可以搜索正则表达式。因此我们在搜索前需要额外判断一下,传入的到底是普通字符串,还是正则表达式。若是普通字符串,则直接去 Map 中查找是否有这个 key;若是正则表达式,则创建一个正则表达式对象,判断 Map 中相同长度的字符串里,是否存在一个能够与这个正则相匹配。

js 复制代码
// 构造函数
const WordDictionary = function (){
	this.words = {} 
}
// 添加字符串方法
WordDictionary.prototype.addWord = function (word) {
  const len = word.length
  if(this.words[len]){
    this.words[len].push(word)
  }else{
    this.words[len] = [word]
}
// 搜索字符串方法
WordDictionary.prototype.searchWord = function(word){
  const len = word.length
  // 没有word长度的数组说明不存在该字符串
	if(!this.words[len]) return false
  // 不包含'.'说明是普通字符串
  if(!word.includes('.')) return this.words[len].includes(word)
  // 创建正则表达式对象
  const reg = new RegExp(word)
  // 只要数组中有一个匹配正则表达式的字符串,就返回true
  return this.words[len].some(item => {
    return reg.test(item)
  })
}

10 颠倒字符串中的单词

题目: 给你一个字符串 s ,颠倒字符串中 单词 的顺序。单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

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

思考: 考虑快慢指针,核心逻辑是:遍历字符串,遇到一个不为空格的元素放下慢指针,快指针一直遍历到空格,此时 slice(慢,快)即可得到单词,依次 unshift 进数组,最后 res.join(' ')即可

js 复制代码
/**
 * @param {string} s
 * @return {string}
 */
var reverseWords = function(s) {
    let start = 0
    let end = 0
    const res = []
    const len = s.length
    while(start < len && end < len){
        if(s[start] !== ' '){
            end = start
            while(s[end] !== ' ' && end < len){
                end++
            }
            res.unshift(s.slice(start, end))
            start = end
        }
        start++
    }
    return res.join(' ')
};
相关推荐
im_AMBER3 分钟前
Web 开发 27
前端·javascript·笔记·后端·学习·web
蓝胖子的多啦A梦28 分钟前
低版本Chrome导致弹框无法滚动的解决方案
前端·css·html·chrome浏览器·版本不同造成问题·弹框页面无法滚动
玩代码30 分钟前
vue项目安装chromedriver超时解决办法
前端·javascript·vue.js
訾博ZiBo1 小时前
React 状态管理中的循环更新陷阱与解决方案
前端
StarPrayers.1 小时前
旅行商问题(TSP)(2)(heuristics.py)(TSP 的两种贪心启发式算法实现)
前端·人工智能·python·算法·pycharm·启发式算法
235161 小时前
【LeetCode】146. LRU 缓存
java·后端·算法·leetcode·链表·缓存·职场和发展
一壶浊酒..1 小时前
ajax局部更新
前端·ajax·okhttp
DoraBigHead2 小时前
React 架构重生记:从递归地狱到时间切片
前端·javascript·react.js
彩旗工作室3 小时前
WordPress 本地开发环境完全指南:从零开始理解 Local by Flywhee
前端·wordpress·网站
iuuia3 小时前
02--CSS基础
前端·css