力扣刷题-字符串篇

222 阅读7分钟

image.png

344. 反转字符串 简单题

编写一个函数,其作用是将输入的字符串反转过来.
输入字符串以字符数组s的形式给出. 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题.

var reverseString = function(s) {
    // 方法一 : return s.reverse(); 直接使用 String.prototype.reverse()
    // 方法二 : 实现reverse功能  O(1)
    let l = 0, r = s.length-1;
    while(l < r) {
        [s[l],s[r]] =[s[r],s[l]];
        l++;
        r--;
    }
    return s;
};

541. 反转字符串 II 简单题

给定一个字符串s和一个整数k,从字符串开头算起,每计数至2k个字符,就反转这2k字符中的前k个字符. 如果剩余字符少于k个,则将剩余字符全部反转.

image.png 注意: 字符串里面的值是无法改变的!!!
对于这题 344. 反转字符串 它的输入字符串是以字符数组s的形式给出的
所以要先将字符串转为字符数组进行处理,处理完之后再转为字符返回.

function reverse(s,l,r){
    // [l,r] 左闭右闭
    while(l<r){
        [s[l],s[r]] = [s[r],s[l]];
        l++;
        r--;
    }
}
var reverseStr = function(s, k) {
    // 字符串转换成字符数组
    let str = Array.from(s);
    for(let i=0; i<str.length; i+=2*k){
        if(i+k<=str.length){
            // 剩余字符大于或等于k ==> 反转前k个字符
            reverse(str,i,i+k-1); //左闭右闭 
        }else {
            // 剩余字符小于k,将剩余字符全部反转
            reverse(str,i,str.length-1//左闭右闭 
        }
    }
    // 返回值为字符串
    //str.toString() ==> 形如 : "b,a,c,d,f,e,g"
    return str.join(""); //=> 形如 :"bacdfeg" 
};

剑指 Offer 05. 替换空格 简单题

image.png

不要借助额外的辅助空间!!!
    首先扩充数组到每个空格替换成“%20”之后的大小.
然后从后向前替换空格,也就是双指针法,过程如下: i指向新长度的末尾,j指向旧长度的末尾.

扩充数组 + 双指针法 (空间复杂度O(1))

var replaceSpace = function(s) {
    //return s.replace(" ","%20"); //"We%20are happy."只能替换一处
    // 解法: 扩充数组 + 双指针法
    // 字符串无法改变值 ==> 字符数组
    let str = Array.from(s);
    // 统计空格数
    let count = 0;
    for(let i=0; i<str.length; i++){
        if(str[i] == " ") count++;
    }
    // 使用双指针
    let left = str.length-1,right = str.length-1 + 2*count;
    while(left>=0) {
        if(str[left]== " "){
            // 是空格 ==> %20 替换
            str[right--] = '0';
            str[right--] = '2';
            str[right--] = '%'; 
        } else {
            // 不是空格
            str[right--] = str[left];
        }
        left--;
    }
    return str.join("");
};

补充: image.png 其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后再从后向前进行操作,从前往后填充,时间复杂度 O(n^2).

151. 反转字符串中的单词 中等题

image.png

image.png

注意:不要使用辅助空间,空间复杂度要求为O(1)

 解题思路:

  • 移除多余空格
  • 将整个字符串反转
  • 将每个单词反转

方法一: 不要使用辅助空间

// 删除多余空格
function removeSpace(str) {
    //"  hello world " 
    // 思考: 什么样的空格要删?  ==> i为0 || 上一个是空格 || 末尾的空格
    // 使用双指针 ==> 数组元素是不能删除的,我们只能是覆盖(因为数组在内存空间中是连续存储的)
    let slow = 0,fast = 0;
    while(fast<str.length){
        //移除开始位置和重复的空格
        if(str[fast]== ' ' && (fast == 0 || str[fast-1] == " ")){
            fast++;
        } else {
            // 需要保留的字符和空格
            str[slow++] = str[fast++];
        }
    }
    //移除末尾的空格 (末尾可能会有一个空格)
    str.length = str[slow-1] == ' ' ? slow-1: slow;
}
// 翻转从 start 到 end 的字符
function reverse(str,start,end){
    while(start < end){
        [str[start],str[end]] = [str[end],str[start]];
        start++;
        end--;
    }
}
var reverseWords = function(s) {
    // js中字符串是不可变类型 
    // s字符串 ==> 字符数组
    let str = Array.from(s);
    // O(1)空间复杂度 原地解法
    //step1 : 处理多余的空格
    removeSpace(str);
    //step2 :  翻转字符数组
    reverse(str,0,str.length-1);// 左闭右闭
    //step3 :  翻转单词
    //"the sky is blue"
    let start = 0;
    for(let i =0; i<str.length; i++){
        // 什么情况下要翻转??? 后一个元素为空格,或当前为最后一个元素
        if(str[i+1] == ' ' || i+1 == str.length){
            // 翻转单词
            reverse(str,start,i);
            i++;
            start = i+1;
        }
    }
    return str.join("");
};

方法二 : 创建了额外的数组空间  (不推荐)

var reverseWords = function(s) {
    let res = new Array();
    let i = s.length-1;
    //处理尾随空格
    while(s[i]==" ") i--;

    while(i>=0){
        // 跳过非空格,找到空格位置(或者i=-1位置)
        while(i>=0 && s[i] != " ") i--;
        let j = i+1// j为单词的起始位置
        while(j<s.length && s[j] !=' '){
            res.push(s[j++]);
        }
        // 处于空格或者i<0的情况
        while(i>=0 && s[i] == " ") i--;
        // 来到非空格 或 i=-1位置
        if(i != -1){
            // 添加上一个空格
            res.push(' ');
        }
    }
    return res.join("");
};

剑指 Offer 58 - II. 左旋转字符串 简单题

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab".

image.png

思路: 不申请额外空间,只在本串上操作 空间复杂度 O(1)

整体反转 + 局部反转 达到左旋转的目的
具体步骤为
1.反转 区间为前n的子串
2.反转 区间为n到末尾的子串
3.反转整个字符串
也可以先反转整个字符串,再反转区间的子串
最后就可以得到左旋转n的目的,而不用定义新的字符串,完全在本串上操作.

image.png

总结

反转字符串
344.反转字符串 使用双指针!!!
541. 反转字符串II 给反转加上了一些额外的条件,当需要固定规律一段一段去处理字符串时,要多想想如何在for循环的表达式上做文章.
151.翻转字符串里的单词 对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路.
本题 左旋转字符串 与151.翻转字符串里的单词类似,先整体反转再局部反转.

怎么看使用substr做这道题???
使用 substr和反转 时间复杂度是一样的,都是O(n),但使用substr申请了额外空间,空间复杂度是O(n),而反转方法的空间复杂度是O(1).

28. 找出字符串中第一个匹配项的下标 中等题

在一个串中查找是否出现过另一个串,这是KMP的看家本领.
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始). 如果不存在,则返回-1.

说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题. 对于本题而言,当 needle 是空字符串时我们应当返回 0.

KMP:主要应用在字符串匹配上.
在一个串中查找是否出现过另一个串,这是KMP的看家本领

KMP: 当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配 (简单点说就是,当出现字符串不匹配时,模式串能一下子跑到对它匹配最高效的位置,最高效就是说让它最快匹配,而不是向暴力匹配一样,从模式串头开始匹配。。。)

那么这一下子模式串如何跑到对它最高效的位置呢?

next数组登场!!! --> 记录已经匹配的文本内容 (KMP的重点)

next数组 ---> 前缀表(用于回退,记录了模式串与主串不匹配时,模式串应该从哪里开始重新匹配)

前缀表统一不减一 image.png image.png

// 前缀表统一不减一
function getNext(needle){
    let next = [];
    let j = 0;
    next.push(j);
    for(let i = 1; i < needle.length; i++){
        // j 是与i进行匹配 
        // needle[j]与 needle[i] 能匹配到,那么needle[j+1] 就能与needle[i+1]进行匹配
        // 那么当needle[j] != needle[i] 时 needle[i+1]将与谁进行匹配???
        while(j>0 && needle[i] !== needle[j]){
            j = next[j-1];
        }
        if(needle[i] == needle[j]) j++;
        // next[i] 存入的值是 next【i+1】要匹配的下标
        next.push(j);
    }
    return next;
}
var strStr = function(haystack, needle) {
    if(needle.length == 0return 0;
    let next = getNext(needle);
    let j =0;
    for(let i=0; i<haystack.length; i++){
        //当前的needle[j] 与 haystack[i] 进行匹配
        // 匹配到不相等, j根据next[j-1]找到匹配的位置,直到j为0
        while(j>0 && needle[j] != haystack[i]) {
            j = next[j-1];
        }
        // 如果匹配相等,则j往下走j++
        if(needle[j] == haystack[i]) j++;
        // 如果j到了needle.length 说明已经在haystack匹配到了
        if(j === needle.lengthreturn i-needle.length+1;
    }
    // 如果不存在,返回-1
    return -1;
};

459. 重复的子字符串 简单题

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成.

image.png

数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环.

// 前缀表统一不减一
function getNext(s){
    let next = [];
    let j =0;
    next.push(j);
    for(let i=1; i<s.length;i++){
        while(j>0 && s[i] != s[j]){
            j = next[j-1];
        }
        if(s[i] == s[j]) j++;
        next[i] = j;
    }
    return next;
}

var repeatedSubstringPattern = function(s) {
   if(s.length == 0return false;
   let next = getNext(s);
   // 最长相同前后缀的长度next[next.length-1]不能为0
   // (数组长度-最长相等前后缀的长度)正好可以被数组长度整除
   if(next[next.length-1] !== 0 && 
   s.length % (s.length-next[next.length-1]) == 0return true;
   return false;
};