07 字符串

字符串

  • 解题注意点
  • 题目汇总
    • 一、双指针-反转字符串
        • [344. 反转字符串](https://leetcode.cn/problems/reverse-string/)
        • ☆ [541. 反转字符串 II](https://leetcode.cn/problems/reverse-string-ii/)
        • [151. 颠倒字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/)
        • [剑指 Offer 58 - II. 左旋转字符串](https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/)
    • 二、双指针-字符串填充
        • [剑指 Offer 05. 替换空格](https://leetcode.cn/problems/ti-huan-kong-ge-lcof/)
    • 三、双指针-删除字符串中的字符
        • 例1:删除字符串中多余的空格
        • [151. 颠倒字符串中的单词](https://leetcode.cn/problems/reverse-words-in-a-string/)
        • 例2:删除字符串中所有重复出现的字符
    • 四、KMP系列-匹配\查找字符串
        • [28. 实现 strStr()](https://leetcode.cn/problems/implement-strstr/)
        • [459. 重复的子字符串](https://leetcode.cn/problems/repeated-substring-pattern/)
    • 五、旋转子字符串
        • [面试题 01.09. 字符串轮转](https://leetcode.cn/problems/string-rotation-lcci/)

解题注意点

在做字符串相关的题目时,要注意,不同的编程语言的不同特性:

  • c++:STL库中定义了string类,string类底层实现类似线性表,元素是可以更改的
  • python:string类是不可更改的,因此通常将string转成char类型的数组再进行处理
  • JAVA:string类是不可更改的,通常使用str.toCharArray()函数将字符串str转成char类型数组使用,或新定义一个StringBuilderStringBuffer(线程安全的)类变量使用

题目汇总

一、双指针-反转字符串

反转字符串类型的题目中,不外乎包括如下三类:

  • 字符串整体翻转
    • 直接使用双指针算法,将字符串整体翻转即可
  • 字符串中的局部内部的翻转(例,541.反转字符串II)
    • C++实现:使用双指针算法,不过要注意控制双指针的移动
    • Python实现:(简单)借助Python中的切片以及reversed函数,对要进行翻转的局部尽行重新赋值,赋值方法使用reversed()内置函数,即:arr[letf,right]=reversed(arr[letf,right])
    • 当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章
  • 字符串中的局部之间的翻转(例,151.反转字符串中单词的顺序)
    • C++:采用先整体翻转,然后再局部翻转的方法
      • 整体翻转:双指针
      • 局部反转:双指针
    • Python:(简单)依旧借助Python中的切片、split函数以及reversed函数,直接实现局部的翻转

344. 反转字符串

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

解题思想:
使用O(1)的额外空间:对于string可以更改的编程语言,利用双指针法即可实现。
定义左右两个指针,以相对方向移动,分别交换两个指针指向的元素,直到两个指针相遇。
时间复杂度O(n)。

交换两个元素:

  • Python: s[left],s[right]=s[right],s[left]
  • C++:swap(s[left],s[right])

Python实现:

class Solution(object):
    def reverseString(self, s):
        """
        :type s: List[str]
        :rtype: None Do not return anything, modify s in-place instead.
        """
        # 反转字符串,使用o(1)的空间复杂度
        # 使用相对双指针法
        left,right=0,len(s)-1
        
        while left<right:
            # temp=s[left]
            # s[left]=s[right]
            # s[right]=temp
            s[left],s[right]=s[right],s[left]
            left+=1
            right-=1

☆ 541. 反转字符串 II

题目介绍:
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章

解题思想:
1、最简单的实现:
python借助已有函数,reversed()以及python的切片操作
值得注意点:

  • python切片操作的[startstep]中如果end超出数组边界,那么默认按照数组边界处理
  • Python中:string–>list:arr=list(str)
  •                   list--->string:str="".join(arr)
    
class Solution:
    def reverseStr(self, s: str, k: int) -> str:
        arr=list(s)
        
        left=0
        for left in range(0,len(s),2*k):
            arr[left:left+k]=reversed(arr[left:left+k])
            
        return "".join(arr)
class Solution {
    public String reverseStr(String s, int k) {
        StringBuilder res=new StringBuilder();
        int i=0;
        for(i=0;i<s.length();i+=2*k){
            //首先对前k个进行翻转
            for(int j=Math.min(i+k-1,s.length()-1);j>=i;j--){
                res.append(s.charAt(j));
            }            
            //然后直接将后k个进行复制
            for(int j=i+k;j<Math.min(i+2*k,s.length());j++){
                res.append(s.charAt(j));
            }
        }
        return new String(res);
    }
}

151. 颠倒字符串中的单词

题目介绍:
给你一个字符串 s ,颠倒字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
**输入:**s = “the sky is blue” 输出:“blue is sky the”

解题思想1:
如果追求O(1)的空间复杂度:
C++实现:
1、删除字符串中的多余空格 ★

  • 使用双指针算法,删除字符串中的多余空格(类比:283.删除数组中的0)
    • 定义slow,fast两个指针,slow指向当前位置;fast指向当前元素;
    • 首先:处理字符串左端的空格:循环寻找fast的位置(fast应该指向要保留的元素);
    • 然后:处理字符串中间的空格:如果fast指向的元素为空格且其前一个元素也为空格,则该fast为多余空格;
    • 最后:处理字符串右端的空格:此时slow指针指向的元素应该就是要保留的元素的末尾的下一个位置,但是,如果原字符串末尾有一个空格,则上述操作会将其保留,因此只需要判断当前slow-1是否为空格即可。

2、将字符串整体翻转

  • 双指针方法,左右两个指针

3、然后分别将每个单词进行翻转

  • 双指针方法,左右两个指针,注意因为字符串中包含多个单词,因此要进行多次单词翻转
  • 本人思想:额外定义一个pos指针,每次都让pos去寻找空格的位置,这样就可以控制每个单词的left以及right指针的位置。
class Solution {
    public:
    void delspace(string &s){
        //采用双指针法删除多余的空格,类比“删除数组中的重复元素”的题目
        int slow=0,fast=0;
        //去掉字符串前面的空格
        while(fast<s.size() && s[fast]==' '){
            fast++;
        }
        //去掉字符串中间的多余空格
        while(fast<s.size()){
            if(fast-1>=0 && s[fast-1]==' ' && s[fast]==' '){
                fast++;
            }
            else{
                s[slow++]=s[fast++];
            }
        }
        //去掉字符串末尾的空格:如果原字符串末尾有空格,则上述操作之后,在字符串末尾必然会保留一个空格,就是slow-1
        if(slow-1>=0 && s[slow-1]==' '){
            s.resize(slow-1);
        }
        else{
            s.resize(slow);
        }
    }
    
    void reversedString(string& s) {
        //定义相对指针实现翻转
        int left = 0, right = s.size() - 1;
        
        while (left < right) {
            char temp = s[left];
            s[left] = s[right];
            s[right] = temp;
            left++;
            right--;
        }
    }
    
    void reversedWord(string& s) {
        int left = 0, right = 0, pos = 0;
        while (pos<s.size() && s[pos] != ' ') pos++;
        right = pos - 1;
        
        while (pos<=s.size() ){
            while (left < right) {
                char temp = s[left];
                s[left] = s[right];
                s[right] = temp;
                left++;
                right--;
            }
            left = pos + 1;
            pos++;
            while (pos<s.size() && s[pos] != ' ') pos++;
            right = pos - 1;
        }
    }
    string reverseWords(string s) {
        // 追求O(1)的空间复杂度
        // 自定义函数实现翻转:先去掉多余的空格;然后对字符串整个进行翻转,然后对每个单词进行翻转
        delspace(s);
        reversedString(s);
        reversedWord(s);
        return s;
    }
};

JAVA实现:
空间复杂度O(n)

class Solution {
    public String reverseWords(String s) {
        //使用StringBuilder实现
        s=s.trim();
        StringBuilder sb=new StringBuilder(s);
        //首先将整个sb进行翻转
        reverse(sb,0,sb.length()-1);
        //然后sb中的多余空格删除
        int i=0;
        while(i<sb.length()){
            if(sb.charAt(i)==' '&& sb.charAt(i-1)==' '){
                sb.deleteCharAt(i);
            }else{
                i++;
            }
        }
        //然后将sb中的word分别翻转
        int l=0;
        for(int r=1;r<=sb.length();r++){
            if(r==sb.length() || sb.charAt(r)==' '){
                reverse(sb,l,r-1);
                l=r+1;
            }
        }
        return new String(sb);
    }
    public void reverse(StringBuilder sb,int begin,int end){
        while(begin<end){
            char c=sb.charAt(begin);
            sb.setCharAt(begin,sb.charAt(end));
            sb.setCharAt(end,c);
            begin++;
            end--;
        }
    }

}

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

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

解题思想一:
直接两段字符串拼接返回即可。

class Solution {
    public String reverseLeftWords(String s, int n) {
        return s.substring(n)+s.substring(0,n);
    }
}

解题思想二:(剑指Offer思想-151. 颠倒字符串中的单词一题的迁移)
与151.颠倒字符串中的单词非常相似。将前k个字符看成一个单词,后面的字符看成一个单词,即可类比151的解法。
采用 先整体翻转,然后局部翻转的方式。

C++实现:

class Solution {
    public:
    void reversed(string &s,int left,int right){
        //将给定字符串的[left,right]范围进行翻转
        while(left<right){
            char temp=s[right];
            s[right]=s[left];
            s[left]=temp;
            left++;
            right--;
        }
    }
    
    string reverseLeftWords(string s, int n) {
        //坐旋转字符串,其实跟颠倒字符串中的单词是一种解法
        //如果想使用O(1)的解法,那么类似“颠倒字符串解法”
        //1、现将整个字符串翻转;
        reversed(s,0,s.size()-1);
        //2、然后将两个部分分别翻转
        reversed(s,0,s.size()-n-1);
        reversed(s,s.size()-n,s.size()-1);
        return s;
    }
};

二、双指针-字符串填充

其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。

剑指 Offer 05. 替换空格

题目描述:
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

解题思想:
这道题如果要做到最优,那么可以考虑不使用额外空间。
具体实现如下:

  1. 现将原字符串扩展到替换之后的大小,
  2. 然后采用slow,fast双指针法(类比:283.删除数组中的0的处理),从后向前遍历,对字符串进行替换修改。

注:
之前使用快慢双指针删除数组中的元素的时候,对slow进行赋值时都是一个位置对应赋值一个元素,
但是在该题中,可以看成是一个空格元素替换成“%20”三个元素,因此会存在一个位置赋值三个元素的情况,
如果还是从前向后处理,就会导致需要将后面的元素逐次向后移动的情况,非常消耗时间,
因此,进行改进,将slow指针指向扩充后的字符串末尾,这样就不会存在冲突了。
C++实现:

class Solution {
    public:
    string replaceSpace(string s) {
        // 官方题解思路:
        // 1、首先将字符串扩充至替换后的大小
        // 2、然后从后向前,使用双指针对字符串进行替换修改
        
        int count=0;
        for(int i=0;s[i]!='\0';i++){
            if(s[i]==' '){
                count++;
            }
        }
        int left=s.size()-1;
        s.resize(s.size()+count*2);
        int right=s.size()-1;
        
        while(left<right){
            if(s[left]!=' '){
                s[right--]=s[left];
            }
            else{
                s[right--]='0';
                s[right--]='2';
                s[right--]='%';
            }
            left--;
        }
        return s;
    }
};

JAVA实现:

  • 空间复杂度O(n)
class Solution {
    public String replaceSpace(String s) {
        //JAVA中字符串是不可更改的,因此只能定义一个额外的变量存储结果,因此空间复杂度为O(n)
        StringBuilder sb=new StringBuilder();
        for(int i=0;i<s.length();i++){
            if(s.charAt(i)!=' '){
                sb.append(s.charAt(i));
            }else{
                sb.append("%20");
            }
        }
        return new String(sb);
    }
}

三、双指针-删除字符串中的字符

思想:采用快慢双指针算法删除字符串中的不需要的元素,
时间复杂度为:O(n)

在C++中,如果直接对字符串进行遍历–>调用erase函数删除,
时间复杂度为:O(n^2),因为erase函数本身的时间复杂度为O(n)

例1:删除字符串中多余的空格

题目介绍:

151. 颠倒字符串中的单词

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

解题思想:
删除字符串中的多余空格 ★

  • 使用双指针算法,删除字符串中的多余空格(类比:283.删除数组中的0)
    • 定义slow,fast两个指针,slow指向当前位置;fast指向当前元素;
    • 首先:处理字符串左端的空格:循环寻找fast的位置(fast应该指向要保留的元素);
    • 然后:处理字符串中间的空格:如果fast指向的元素为空格且其前一个元素也为空格,则该fast为多余空格;
    • 最后:处理字符串右端的空格:此时slow指针指向的元素应该就是要保留的元素的末尾的下一个位置,但是,如果原字符串末尾有一个空格,则上述操作会将其保留,因此只需要判断当前slow-1是否为空格即可。
void delspace(string &s){
        //采用双指针法删除多余的空格,类比“删除数组中的重复元素”的题目
        int slow=0,fast=0;
        //去掉字符串前面的空格
        while(fast<s.size() && s[fast]==' '){
            fast++;
        }
        //去掉字符串中间的多余空格
        while(fast<s.size()){
            if(fast-1>=0 && s[fast-1]==' ' && s[fast]==' '){
                fast++;
            }
            else{
                s[slow++]=s[fast++];
            }
        }
        //去掉字符串末尾的空格:如果原字符串末尾有空格,则上述操作之后,在字符串末尾必然会保留一个空格,就是slow-1
        if(slow-1>=0 && s[slow-1]==' '){
            s.resize(slow-1);
        }
        else{
            s.resize(slow);
        }
    }

例2:删除字符串中所有重复出现的字符

题目介绍:
定义一个函数,删除字符串中所有重复出现的字符。
例如,输入,“google”删除之后的结果是“gole”。

解题思想:
Hash table+双指针

  1. 首先需要判断字符是否是重复出现的
    • 使用hash table,首先将数组初始化为全False,遍历字符串,(如果声明字符串中的字符为普通字符,那么可以直接使用数组实现简易hash table:字符的ASCII码作为下标,数组存储False,True)
      • 如果对应hash table中的值为False,则该元素要保留,并且将hash table对应值置为true;
      • 如果对应hash table中的值为False,则表示该元素要删除
  2. 删除掉重复的字符
    • 如何对字符进行删除以及保留?
      • 最直接的方法:直接定义一个空的新的字符串,将要保留的字符添加到该新字符串中,但是空间复杂度为O(N)
      • 使用内置函数:使用内置的删除元素函数,如erase()【c++】,但是时间复杂度为O(n^2)
      • 空间复杂度为O(1)时间复杂度为O(N)的算法:是用双指针遍历,要保留的元素放在字符串首,要删除的元素放在字符串的后面的位置(类比,283.删除数组中的0)

四、KMP系列-匹配\查找字符串

对于字符串匹配(查找)问题,采用KMP算法可以避免重复匹配的问题。
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
KMP的精髓所在就是前缀表,
前缀表:起始位置到下标i之前(包括i)的子串中,有多大长度的相同前缀后缀。
详见,08 KMP算法 .

28. 实现 strStr()

459. 重复的子字符串

五、旋转子字符串

若两字符串互为旋转,则「其一字符串」必然为「另一字符串拓展两倍长度后(循环子串)」的子串。

面试题 01.09. 字符串轮转

题目描述:
字符串轮转。给定两个字符串s1和s2,请编写代码检查s2是否为s1旋转而成(比如,waterbottle是erbottlewat旋转后的字符串)。
** 输入**:s1 = “waterbottle”, s2 = “erbottlewat” ** 输出**:True

方法一:拼接查找子串

  • 首先,如果S1的长度与S2不等,那么一定不可能是旋转字符串
  • 其次,如果是旋转字符串,那么S1+S1中一定存在子串S2
class Solution {
public:
    bool isFlipedString(string s1, string s2) {
        return s1.size()==s2.size() && (s1+s1).find(s2)!=string::npos;
    }
class Solution {
    public boolean isFlipedString(String s1, String s2) {
        //如果s1是由s2轮转得到,则s2+s2构成的循环字符串中正好存在两个s1子串
        if(s1.length()!=s2.length())    return false;
        String s=s2+s2;
        return s.indexOf(s1)!=-1;

    }
}

你可能感兴趣的:(算法,力扣,leetcode)