Javascript字符串操作常见算法题

 字符串匹配(KMP算法)

KMP算法,全称Knuth-Morris-Pratt算法,是一种用于字符串匹配的算法。下面是对KMP算法的详细解析:

一、KMP算法概述

  1. 基本概念
    • KMP算法是由Donald Knuth、Vaughan Pratt和James Morris共同发明的,因此算法名称取自这三位发明人名字的首字母组合。
    • 该算法主要用于解决字符串(主串)中的模式串(子串)定位问题,如“求子串出现的起始位置”、“求子串的出现次数”等。
  2. 核心思想
    • KMP算法对朴素的字符串匹配算法进行了改进,它利用匹配失败时已知的部分匹配信息,保持主串的指针不回溯,通过修改模式串的指针,使模式串尽量地移动到有效的匹配位置。
    • 在匹配失败时,不再按照朴素匹配算法的规则重新回溯主串指针和子串指针,而是保持主串指针不动,尽可能地移动子串指针到有效匹配位置。
  3. 关系推导
    • 假设主串为S,模式串为T。当主串的第i个字符与模式串的第j个字符失配后,主串的第i个字符将与模式串的第k个字符(k<j)继续比较。
    • 此时,需要找到一个位置k,使得模式串的前k-1个字符与主串中失配位置之前的子串相等。这个位置k可以通过部分匹配表(Next数组)来确定。

二、Next数组概述

  1. 含义
    • Next数组是KMP算法的核心,它记录了模式串中每个位置之前的子串的最大相同前后缀长度。
    • 在匹配过程中,当模式串的某个字符与主串的字符不匹配时,可以根据Next数组直接跳转到模式串的下一个可能匹配位置。
  2. 如何求Next数组
    • 初始化Next[0]为-1或0(根据具体实现而定,有的实现中Next[0]不参与匹配过程,因此可以设为-1表示无效)。
    • 对于模式串的每个位置j(从1开始),如果模式串的第j+1个字符与前缀中的某个字符相等(即S[j+1] == S[Next[j]+1]),则Next[j+1] = Next[j] + 1。
    • 否则,需要回溯Next数组,找到一个新的位置k(k = Next[Next[j]-1]),使得S[1...k] = S[j-k+1...j-1],并判断S[j+1]是否等于S[k+1]。如果相等,则Next[j+1] = k + 1;否则,继续回溯直到找到匹配或Next[k]为0为止。
function KMP_SEARCH(text, pattern):  
    next = COMPUTE_NEXT(pattern)  
    n = length(text)  
    m = length(pattern)  
    i = 0  // text的指针  
    j = 0  // pattern的指针  
    while i < n:  
        if j == -1 or text[i] == pattern[j]:  
            i += 1  
            j += 1  
        if j == m:  
            return i - j  // 匹配成功,返回匹配的起始位置  
        else:  
            j = next[j-1]  // 移动模式串指针  
    return -1  // 未找到匹配  
  
function COMPUTE_NEXT(pattern):  
    m = length(pattern)  
    next = array[m]  
    next[0] = -1  // 或0,根据具体实现而定  
    len = 0  
    i = 1  
    while i < m:  
        if pattern[i] == pattern[len]:  
            len += 1  
            next[i] = len  
            i += 1  
        else:  
            if len > 0:  
                len = next[len-1]  
            else:  
                next[i] = 0  
                i += 1  
    return next

三、KMP算法的应用场景

KMP算法广泛应用于各种需要快速、高效字符串匹配的场景中,如:

  • 字符串搜索:在大规模文本数据中快速定位特定字符串。
  • 字符串编辑:处理字符串中的替换、插入和删除操作。
  • 自动补全:实现搜索引擎的自动完成功能。
  • 基因序列匹配:在生物信息学领域中匹配DNA或RNA序列。
  • 代码编辑器:实现代码编辑器中的代码提示功能等。

题目描述
在一个字符串中查找另一个字符串的出现位置,可以使用朴素的字符串匹配算法,但KMP(Knuth-Morris-Pratt)算法更高效。

解题思路

  • KMP算法通过预处理模式串(要查找的字符串)来构建一个部分匹配表(也叫做“最长公共前后缀数组”),该表记录了每个位置的字符串前缀的最长公共前后缀长度。
  • 在匹配过程中,当发现不匹配时,可以根据部分匹配表直接跳转到下一个可能匹配的位置,而不需要像朴素算法那样每次只移动一个字符的位置。

代码示例(简化版,不包含部分匹配表的构建过程):

// 注意:这里只是展示了KMP算法的思路,实际使用时需要完整实现部分匹配表的构建  
function kmpSearch(text, pattern) {  
    // 假设已经有了部分匹配表 next[]  
    let next = []; // 这里应该是通过预处理pattern得到的部分匹配表  
  
    let i = 0; // text的索引  
    let j = 0; // pattern的索引  
  
    while (i < text.length) {  
        if (text[i] === pattern[j]) {  
            i++;  
            j++;  
        }  
  
        if (j === pattern.length) {  
            // 找到匹配,返回匹配起始位置  
            return i - j;  
        } else if (i < text.length && text[i] !== pattern[j]) {  
            // 不匹配,根据部分匹配表移动j  
            if (j !== 0) {  
                j = next[j - 1];  
            } else {  
                i++;  
            }  
        }  
    }  
  
    // 未找到匹配,返回-1  
    return -1;  
}  
  
// 注意:实际使用时,需要补充next数组的构建逻辑

反转字符串

题目描述
给定一个字符串,要求输出其反转后的字符串。

解题思路

  • 使用双指针方法,一个指针从字符串的开头开始,另一个指针从字符串的末尾开始。
  • 交换两个指针所指向的字符,然后同时向中间移动指针,直到两个指针相遇。
  • 这种方法的时间复杂度为O(n),空间复杂度为O(1)。
/**
 * @param {character[]} s
 * @return {void} Do not return anything, modify s in-place instead.
 */
var reverseString = function(s) {
    // s.reverse(); 修改原数组
    //双指针交换,一个指向字符串头一个指向字符串尾
    let length=s.length;
    let mid=length/2;
    let right=length-1;
    for(let left=0;left<mid;left++){
        [s[left],s[right]]=[s[right],s[left]]
        right--;
    }
};

反转字符串II 

/**
 * @param {string} s
 * @param {number} k
 * @return {string}
 */
var reverseStr = function(s, k) {
    //字符串=》字符数组
    let sArr=s.split('');
    let length=s.length;
    //每计数2*k for!!!
    for(let i=0;i<length;i+=2*k){
        let left=i;
        //right指向要反转的最后字符们不要mid!
        //三元表达式,判断i+k-1右边的指针是否超过字符长度(2k与2k>=k),没超过就是交换前k
        //超过标识字符小于k,就全交换剩下的字符
        let right=(i+k-1)>=length?length-1:i+k-1;
        while(left<right){
            [sArr[left],sArr[right]]=[sArr[right],sArr[left]];
            left++;
            right--;
        }
    }

    // let start=0;
    // let end=k-1;
    // let mid=k/2;
    // let times=0;
    // while(true){
    //     if(length<k){
    //         start=2*k*times;
    //         end=length-1;
    //         let mid=2*k+length/2;
    //         for(;start<end;start++){
    //             //交换
    //             [sArr[start],sArr[end]]=[sArr[end],sArr[start]];
    //             //移动指针;
    //             end--;
    //         }
    //         break;
    //     }
    //     if(length>=k&&length<2*k){
    //         start=2*k*times;
    //         //end,设置为2k+k-1,共n个数!
    //         end=2*k*times+k-1;
    //         for(;start<end;start++){
    //             //交换
    //             [sArr[start],sArr[end]]=[sArr[end],sArr[start]];
    //             //移动指针;
    //             end--;
    //         }
    //         break;
    //     }
    // for(;start<end;start++){
    //     //交换
    //     [sArr[start],sArr[end]]=[sArr[end],sArr[start]];
    //     //移动指针;
    //     end--;
    // }
    // times++;
    // length=length-2*k;
    // }
    //字符数组=》字符串
    return sArr.join('');
};

替换数字 

题目描述
给定一个字符串和若干对替换规则,要求按照规则替换字符串中的部分子串。

解题思路

  • 可以使用正则表达式来匹配和替换字符串中的子串。
  • 遍历替换规则,对每个规则构建一个正则表达式,并使用String.prototype.replace()方法进行替换。
  • 注意,替换顺序可能会影响最终结果,因此可能需要按照特定顺序(如从长到短)来处理替换规则。
function replaceNumber(s){
    let length=s.length;
    let inputArr=s.split("");
    let baseCode="a".charCodeAt(0);
    for(let i=0;i<length;i++){
        let curCode=inputArr[i].charCodeAt(0);
        if(!(curCode-baseCode>=0&&curCode-baseCode<26)){
            inputArr[i]="number"
        }
    }
    return inputArr.join("");
    // //2.正则表达式
    // let regex=/[a-z]/g
    // const replacedS=s.replace(regex,"number")
    // return replacedS;
}

反转单词 (先整体后局部,先反转所有字符,进一步再反转单词)

    //用已经存在的api
    let wordArr=s.split(" ");
    let filtered=wordArr.filter(item=>item!=="");
    let length=filtered.length;
    let right=length-1;
    for(let left=0;left<length;left++){
        if(left>=right){
            break;
        }
        [filtered[left],filtered[right]]=[filtered[right],filtered[left]];
        //这里是已经在一个循环中了,你还来一个left++,哭死@jing
        right--;
    }
    return filtered.join(" ");
/**
 * @param {string} s
 * @return {string}
 */
var reverseWords = function(s) {
    // //用已经存在的api
    // let wordArr=s.split(" ");
    // let filtered=wordArr.filter(item=>item!=="");
    // let length=filtered.length;
    // let right=length-1;
    // for(let left=0;left<length;left++){
    //     if(left>=right){
    //         break;
    //     }
    //     [filtered[left],filtered[right]]=[filtered[right],filtered[left]];
    //     //这里是已经在一个循环中了,你还来一个left++,哭死@jing
    //     right--;
    // }
    // return filtered.join(" ");

    //要求不用额外空间,即在原来字符串上操作!!!

    let strArr=Array.from(s);//[' ','h','l'...]

    // 1.移除多余空格
    // 1.1正则表达式 str.replace(/\s+/g, ' ').trim();
    //1.2 手动实现 ['h','e'...]
     removeSpace(strArr);
    //2.将整个字符串反转(eulb si)
     reverseManual(strArr,0,strArr.length-1)
    //3.将每个单词反转(blue is)!!!!
    let start=0;
    //等于str.length表示已经遍历完成
    for(let i=0;i<=strArr.length;i++){
        //每次碰到空格符号或到字符串最后的后一项
        if(strArr[i]===" "||i===strArr.length){
            reverseManual(strArr,start,i-1);
            start=i+1;
        }
    }
    return strArr.join('')


};
function removeSpace(strArr){
    //双指针用于去除多余空格[前后多余,中间多个空格->一个空格]
    let fast=0;
    let slow=0;
    for(;fast<strArr.length;fast++){
        //前面或中间多个
        if(strArr[fast]===" "&&(fast===0||strArr[fast-1]===" "))continue;
        else{
            strArr[slow++]=strArr[fast];
        }
    }
    //移除 末尾空格,通过修改数组长度
    strArr.length=strArr[slow-1]===" "?slow-1:slow;
}
function reverseManual(strArr,start,end){
    let left=start;
    let right=end;
    while(left<right){
         [strArr[left],strArr[right]]=[strArr[right],strArr[left]];
         right--;
         left++;
    }
}

右翻转(abcdefg->fgabcde,与上类似,先整体后局部/先局部后整体)

扫描二维码关注公众号,回复: 17409488 查看本文章
// JS中字符串内不可单独修改

const readline = require('readline')

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
})

const inputs = []; // 存储输入

rl.on('line', function(data) {
    inputs.push(data);

}).on('close', function() {
    const res = deal(inputs);
    // 打印结果
    console.log(res);
})

// 对传入的数据进行处理
function deal(inputs) {
    let [k, s] = inputs;
    const len = s.length - 1;
    k = parseInt(k);
    str = s.split('');
    
    str = reverseStr(str, 0, len - k)
    str = reverseStr(str, len - k + 1, len)
    str = reverseStr(str, 0, len)

    return str.join('');
}

// 根据提供的范围进行翻转
function reverseStr(s, start, end) {
    
    while (start < end) {
        [s[start], s[end]] = [s[end], s[start]]
        
        start++;
        end--;
    }

    return s;
}

回文字符串判断

题目描述
判断一个字符串是否是回文串(即正读和反读都相同的字符串)。

解题思路

  • 可以使用双指针方法,一个从字符串开头开始,一个从字符串末尾开始,比较两个指针所指向的字符是否相同。
  • 也可以先将字符串反转,然后比较反转后的字符串和原字符串是否相同。

代码示例(双指针方法):

function isPalindrome(s) {  
    let left = 0;  
    let right = s.length - 1;  
  
    while (left < right) {  
        if (s[left] !== s[right]) {  
            return false;  
        }  
        left++;  
        right--;  
    }  
  
    return true;  
}  
  
console.log(isPalindrome("racecar")); // 输出 true

猜你喜欢

转载自blog.csdn.net/m0_55049655/article/details/143279341