字符串算法一般解题指南(前端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(' ')
};
相关推荐
糊涂涂是个小盆友6 分钟前
前端 - 使用uniapp+vue搭建前端项目(app端)
前端·vue.js·uni-app
浮华似水29 分钟前
Javascirpt时区——脱坑指南
前端
王二端茶倒水32 分钟前
大龄程序员兼职跑外卖第五周之亲身感悟
前端·后端·程序员
_oP_i37 分钟前
Web 与 Unity 之间的交互
前端·unity·交互
钢铁小狗侠39 分钟前
前端(1)——快速入门HTML
前端·html
凹凸曼打不赢小怪兽1 小时前
react 受控组件和非受控组件
前端·javascript·react.js
狂奔solar1 小时前
分享个好玩的,在k8s上部署web版macos
前端·macos·kubernetes
qiyi.sky1 小时前
JavaWeb——Web入门(8/9)- Tomcat:基本使用(下载与安装、目录结构介绍、启动与关闭、可能出现的问题及解决方案、总结)
java·前端·笔记·学习·tomcat
清云随笔2 小时前
axios 实现 无感刷新方案
前端
鑫宝Code2 小时前
【React】状态管理之Redux
前端·react.js·前端框架