头图

🧑‍💻JavaScript算法与数据结构-HowieCong

务必要熟悉JavaScript使用再来学!

一、基础算法技能

会根据基础算法技能去衍生问题

(1)反转字符串

  • 反转字符串可以直接调用相关API即可
// 定义被反转的字符串
const str = 'howiecong'

// 定义反转后的字符串
const res = str.split('').reverse().join('')

console.log(res)// gnoceiwoh

(2)判断一个字符串是否是回文字符串

  • 回文字符串,就是正着和倒着都是一样的字符串
  • eg:'yessey'
  • 编码实现:判定是否为回文字符串
function isPalindrome(str){
    // 先反转字符串
    const reversedStr = str.split('').reverse().join('')
    
    // 判断反转前后是否相等
    
    return reversedStr === str
}
  • 回文字符串另一个特性:如果从中间位置分开,那么两边的子串在内容上是完全对称的,所以可以结合对称性来判断,编码实现如下:
function isPalindrome(str){
    // 字符串的长度
    const len = str.length
    
    // 遍历前半部分,判断和后半部分是否对称
    for(let i = 0 ; i < len/2; i++){
        if(str[i] !== str[len-i-1]){
            return false
        }
    }
    return true
}

二、真题分析

(1)回文字符串的衍生问题

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

示例

输入: "aba"\
输出: True\
示例 2:\
输入: "abca"\
输出: True\
解释: 你可以删除c字符。\
注意: 字符串只包含从 a-z 的小写字母。字符串的最大长度是50000。

1.思路分析
  • 字符串中如果有“回文”这个关键字,就要想到对称性和双指针
  • 第一步:初始化两个指针,一个指向字符串头部,另一个指向尾部

image.png

  • 第二步:如果两个指针相等,那么符合回文字符串对称性的要求

image.png

  • 第三步:如果两个指针所指的字符不等,就不对称了,就可以尝试删除看看,分别在左指针字符和右指针字符进行跳过,看看区间在[left + 1,right]或[left,right - 1]的字符串是否回文,如果是的话,那么就意味着删掉被跳过的字符,整个字符串都回文

image.png

  • 这里我们跳过了b,[left + 1,right]的区间就是[2,2],它对应c这个字符,单个字符一定回文,这样一来,删掉b之后,左右指针所指的内部区间是回文的,外部区间也是回文的
2.编码实现
const vaildPalindrome = function(s){
    // 字符串的长度
    const let = s.lenght

    // i左指针;j右指针
    let i = 0,j = len - 1;
        
    // 当左右指针满足对称时,一起向中间前进
    while(i < j && s[i] === s[j]){
        i++
        j--
    }
    
    // 判断跳过左指针元素后字符串是否回文
    if(isPalindrome9(i+1,j)){
        return true
    }
    
    // 判断跳过右指针元素后字符串是否回文
    if(isPalindrome9(i+1,j)){
        return true
    }
    
    // 根据方法,判断字符串是否回文
    function isPalindrome(st,ed){
        while(st < ed){
            if(s[st] !== s[ed]){
                return false
            }
            st++
            ed--
        }
        return true
    }
    // 默认返回false
    return false
}

(2)字符串匹配问题——正则表达式

原题:

设计一个支持以下两种操作的数据结构:\
void addWord(word)\
bool search(word)\
search(word) 可以搜索文字或正则表达式字符串,字符串只包含字母 . 或 a-z 。\
. 可以表示任何一个字母。

示例:

addWord("bad")\
addWord("dad")\
addWord("mad")\
search("pad") -> false\
search("bad") -> true\
search(".ad") -> true\
search("b..") -> true\
说明:\
你可以假设所有单词都是由小写字母 a-z 组成的。

1.思路分析
  • 要求字符串可以被添加,也可以被搜索,这就意味着字符串在添加时一定要被存在某处,键值对存储,可以用Map(或对象字面量来模拟Map)
  • 降低查找时的复杂度,可以考虑以字符串的长度为key,相同长度的字符串存在在一个数字中,可以提高后续定位的效率
  • search这个API,既可以搜索文字,也可以搜索正则表达式,因此我们在搜索需要额外判断一个,传入是不是普通字符串,还是正则表达式,如果是普通字符串,可以直接Map查找是否有这个key,如果是正则表达式,则要创建一个正则表达式对象,判断Map相同长度的字符串,是否存在一个能够与这个正则相匹配
2.编码实现
// 构造函数
const WordDictionary = function(){
    // 初始化一个对象字面量,承担Map
    this.words = {}
}

// 添加字符串
WordDictionary.prototype.addword = function(word){
    // 如果字符串对应长度的数组已经存在,则只添加
    if(this.word[word.length]){
        this.words[words.lenght].push(word)
    }else{
        // 如果字符串对应长度的数组不存在,则只添加
        this.words[words.lenght] = [word]
    }
};

// 搜索方法
WordDictionary.prototype.search = function(word){
    // 如果字符串长度在Map中对应的数组根本不存在,可以判断字符串不存在
    if(!this.word[word.length]){
        return false
    }
    
    // 缓存目标字符串鹅长度
    const len = word.length
    
    // 如果字符串不包含'.'那么是普通字符串
    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)
    }
};

(3)正则表达式——字符串与数字之间的转换问题

原题:

请你来实现一个 atoi 函数,使其能将字符串转换成整数。\
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。\
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。\
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。\
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。\
在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明: 假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−2^31,  2^31 − 1]。如果数值超过这个范围,请返回  INT\_MAX (2^31 − 1) 或 INT\_MIN (−2^31) 。

示例:

示例 1:\
输入: "42"\
输出: 42

示例 2:\
输入: " -42"\
输出: -42\
解释: 第一个非空白字符为 '-', 它是一个负号。\
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。

示例 3: 输入: "4193 with words"\
输出: 4193\
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。

示例 4: 输入: "words and 987"\
输出: 0\
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。 因此无法执行有效的转换。

示例 5:\
输入: "-91283472332"\
输出: -2147483648\
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。因此返回 INT\_MIN (−2^31) 。

1.思路分析
  • 该函数根据需要丢且无用的开头空格字符,直到寻找到第一个非空格的字符为止,允许字符串头部出现空格,但是你在处理的时候要想办法把它丢出去,不能干扰计算
  • 当寻找到第一个非空字符为正或负号,则将该符号与之后面尽可能多的连续数字组合。作为该整数的正负号,允许字符串的第一个有效字符为“+”或者“-”,不要丢它出去,它的计算是有意义的
  • 字符串除了有效的整数部分之后,也可能会存在多余的字符,这些字符可以被忽略,对于函数不应该造成影响,匹配的时候,连续整数之外的部分应该被消除
  • 消除空格的两个方法:1.直接使用string的trim方法,它是js的一个原生方案,可以去除字符串的头尾空格 2.在匹配的时候,匹配空格,正则匹配符为\s*,意味着匹配0个或多个空格,但是不把它放在捕获组里——这种方法会加常用,正则匹配过程中,所有消除的动作都可以通过将匹配的结果排除在捕获组之外来实现
let str = '      +10086'
str.trim() // '+10086'
  • 捕获组——就是正则表达式中被小括号括住的部分,在这道题里面,我们需要从字符串提取的只有+/-符号以及后面的数字而已,同时这个字符串需要满足 “可能存在的空格+正负号+数字字符串+其他字符内容” 才算合法
/\s*([-\+]?[0-9]*).*/
  • 解释以下上方正则表达式

    • \s这个符号,意味着空字符,匹配回车、空格、换行等空白区域,这里,用来被匹配空格,这个符号,跟在其他符号后面,意味着前面这个符号可以出现0次或者多次,\s,这里的意思就是空格出现0次或者多次,都可以被匹配到
    • ()圈住的内容,就是我们要捕获额外存储的东西
    • []中的匹配符直接就是 或 的关系,就是只要匹配到其中一个就可以,这里因为+本身是一个特殊作用的正则匹配符,要让它回到+字符,所以用\来完成转义
    • [0-9]*就意味这恶0-9的整数,能匹配到0个和多个就能匹配成功
    • .这个是任意字符的意思,.*用于字符串尾部匹配非数字的任意字符,是被排除捕获组之外的,也不会被额外存储
  • 获得捕获结果

    • JS正则里面,test()方法返回是一个布尔值,单纯判断“是否匹配”,要想获取匹配的结果,需要调match()方法:
    const reg = /\s*([-\+]?[0-9]*).*/
    const groups = str.match(reg)
    • match()方法是一个字符串中执行查找匹配的String方法,返回一个数组,未匹配会返回null
    • 如果正则表达式尾部有g标志,match()会返回与完整正则表达式匹配的所有结果,但不会返回捕获组
    • 这里没有g标志,match返回第一个完整匹配(作为数组的第0项)及其相关的捕获组(作为数组的第1及第1+项)
    • 因此可以从group[1]拿到我们捕获的结果
  • 判断卡口的最后一步,就是捕获的结果转换为数字,看看也没有超出题目要求的范围
2.编码实现
// 入参是一个字符串
const myAtoi = function(str){
    // 编写正则表达式
    const reg = /\s*([-\+]?[0-9]*).*/
    // 得到捕获组
    const groups = str.match(reg)
    // 计算最大值
    const max = Math.pow(2,31) - 1
    // 计算最小值
    const min = -max - 1
    // targetNum 用于存储转化出来的数字
    let targetNum = 0
    // 如果匹配成功
    if(groups){
        // 尝试转化捕获到的结构
        targetNum = +groups[1]
        // 即便成功,也可能出现非数字的情况,比如就一个+
        if(isNaN(targetNum)){
            // 不能进行有效的转换时,请返回0
            targetNum = 0
        }
    }
    
    // 卡口判断
    if(targetNum > max){
        return max
    }else if(targetNum < min){
        return min
    }
    
    //返回转换结果
    return targetNum
}

❓其他

1. 疑问与作者HowieCong声明

  • 如有疑问、出错的知识,请及时点击下方链接添加作者HowieCong的其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
  • 若想让作者更新哪些方面的技术文章或补充更多知识在这篇文章,请及时点击下方链接添加里面其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
  • 声明:作者HowieCong目前只是一个前端开发小菜鸟,写文章的初衷只是全面提高自身能力和见识;如果对此篇文章喜欢或能帮助到你,麻烦给作者HowieCong点个关注/给这篇文章点个赞/收藏这篇文章/在评论区留下你的想法吧,欢迎大家来交流!

2. 作者社交媒体/邮箱-HowieCong


HowieCong
2 声望0 粉丝

大前端开发 => AI 小菜鸡!虚心好学!欢迎一起交流!