【算法】字符串 汇总

207 阅读6分钟

344.反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。

  示例 1:

输入: s = ["h","e","l","l","o"]
输出: ["o","l","l","e","h"]

方法1 用swap()

class Solution { //方法1
public:
    void reverseString(vector<char>& s) {
        char tmp;
        for (int i = 0; i < s.size() / 2; i++) {
            swap(s[i], s[s.size() - 1 - i]);
        }
    }
};

方法2 迭代器

class Solution { // 方法2 迭代器
public:
    void swapchars(vector<char>::iterator left, vector<char>::iterator right) { //左闭右开
        for (int i = 0, n = right - left; i < n / 2; i++) {
            right--;
            swap(*left, *right);
            left++;
        }
    }
    void reverseString(vector<char>& s) {
        vector<char>::iterator left = s.begin();
        vector<char>::iterator right = s.end();
        swapchars(left, right);
        
    }
};

541. 反转字符串 II

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

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

示例 1:

输入: s = "abcdefg", k = 2
输出: "bacdfeg"

我的思路是先交换,留下最后一个小尾巴再判断

class Solution {
public:
    void chag2k(string &s, int i, int j) { // 左闭右闭  
        while (i < j) {
            swap(s[i], s[j]);
            i++, j--;
        }
    }
    string reverseStr(string s, int k) { // 我的思路是先交换,留下最后一个小尾巴再判断
        int tmp = 2 * k; // 如果一开始为0,循环里面每次先判断是否剩余字符少于 k 个就不用拼接了
        while (tmp < s.size()) {
            chag2k(s, tmp - (2 * k), tmp - k - 1);
            tmp += (2 * k);
        }
        if ( (s.size() - (tmp - (2 * k))) < k) {
            chag2k(s, tmp - (2 * k), s.size() - 1);
        } else {
            chag2k(s, tmp - (2 * k), tmp - k - 1);
        }
        return s;
    }
};

第二种思路是直接判断,每次都判断剩余字符数量和k的关系,这样更方便

class Solution {
public:
    string reverseStr(string s, int k) {
        for (int i = 0; i < s.size(); i += (2 * k)) {
            // 1. 每隔 2k 个字符的前 k 个字符进行反转
            // 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
            if (i + k <= s.size()) {
                reverse(s.begin() + i, s.begin() + i + k );
            } else {
                // 3. 剩余字符少于 k 个,则将剩余字符全部反转。
                reverse(s.begin() + i, s.end());
            }
        }
        return s;
    }
};

剑指 Offer 05. 替换空格

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

示例 1:

输入: s = "We are happy."
输出: "We%20are%20happy."

这个主要是要会扩充,因为要插入三个字符,s[i]只能容得下一个字符,不能s[i]='%20'这样,

class Solution {
public:
    string replaceSpace(string s) {
        int numk = 0;
        int soldsize = s.size();
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ' ') {
                // 要扩充,s[i]只能容得下一个字符
                // s[i] = '%20'; // 这个不对
                numk++;
            }
        }
        s.resize(s.size() + numk * 2, 0); // 用考虑\0吗
        for (int i = soldsize - 1, j = s.size() - 1; i >= 0; i--, j--) {
            if (s[i] != ' ') { // 这个其实也是双指针方法,i慢j快,i在前面(左边),j追赶i
                s[j] = s[i];
            } else if (s[i] == ' ') {
                s[j] = '0';
                s[j - 1] = '2';
                s[j - 2] = '%';
                j -= 2;
            }
        }
        return s;
    }
};

151. 反转字符串中的单词

给你一个字符串 s ,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

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

示例 1:

输入: s = "the sky is blue"
输出: "blue is sky the"

这个题还行,就是一开始要处理字符串,移除多余空格;然后字符串大反转,用swap;最后再每个单词的反转

class Solution {
public:
    // 1.移除多余空格
    void removeMoreSpace(string &s) {
        // 头
        while (s[0] == ' ') {
            s.erase(s.begin());
        }
        // 尾
         while (s[s.size() - 1] == ' ') {
            s.erase(s.end() - 1);
        }
        // 中间
        int slow = 1, fast = 2;
        while(fast < s.size() - 1) {
            if (s[fast] == ' ' && s[slow] == ' ') {
               s.erase(s.begin() + slow); 
            } else {
                slow++;
                fast++;
            }
        }
    }
    // 2.字符串反转 swap就好
    void swapchars(string::iterator left, string::iterator right) { //左闭右开
        for (int i = 0, n = (right - left); i < n / 2; i++) {
            right--;
            swap(*left, *right);
            left++;
        }
    }
    
    string reverseWords(string s) {
        // 返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格
        // 1.移除多余空格
        removeMoreSpace(s);
        
        // 2.字符串反转 swap就好
        swapchars(s.begin(), s.end());
        
        // 3.单词反转
        string::iterator slow = s.begin();
        string::iterator fast = s.begin();

        //for ( ; slow != s.end(); fast++) {
        while (slow != s.end() + 1) {
            if (*fast == ' ' || fast == s.end()) {
                swapchars(slow, fast);
                if (fast == s.end()) break;
                fast++; // 最后一个iterator越界了 
                slow = fast;
            } else {
                fast++;
            }
        }
        return s;
        // void reverse(string& s, int start, int end){ //翻转,区间写法:左闭右闭 []
        // for (int i = start, j = end; i < j; i++, j--) {
        //     swap(s[i], s[j]);
        // }
        // 我的解法的最后一个iterator越界了,之后*解引用就会报错,所以说还是写成下标的形式判断更方便,
        // for (int i = 0; i <= s.size(); ++i) {}这种判断i <= s.size()就很方便
        // 写成i <= s.size()就不用if (fast == s.end()) 
    }
};

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

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

  示例 1:

输入: s = "abcdefg", k = 2
输出: "cdefgab"

方法1 添加和删除

class Solution { //方法1 添加和删除
public:
    string reverseLeftWords(string s, int n) {
        for (int i = 0; i < n; i++) {
            s.push_back(s[0]);
            s.erase(s.begin());
        }
        return s;
    }
};

第二种方法好像脑筋急转弯哦,翻转三次,开始到n,n到结束,最后开始到结束

class Solution { //方法2 翻转
public:
    string reverseLeftWords(string s, int n) {
        reverse(s.begin(), s.begin() + n);
        reverse(s.begin() + n, s.end());
        reverse(s.begin(), s.end());
        return s;
    }
};

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

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回  -1

示例 1:

输入: haystack = "sadbutsad", needle = "sad"
输出: 0
解释: "sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

暴力方法,判断指针i和要查找的下标首元素是否相等,若相等则进行后续查找,

class Solution { //暴力通过
public:
    int strStr(string haystack, string needle) {
        int fak = 0;
        for (int i = 0; i < haystack.size(); i++) {
            if (haystack[i] == needle[0]) { //可能相等
                for (int i1 = i, j = 0; j < needle.size(); j++, i1++) {
                    if (haystack[i1] != needle[j]) {
                        fak = -1;
                        break;
                    }
                }
                if (fak != -1) return i;
                else fak = 0;
            }
        }
        return -1;
    }
};

方法2:KMP算法里的next数组,知道了next数组就是前缀表,再分析为什么要是前缀表而不是什么其他表

前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

还没学会,等更新

459. 重复的子字符串

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

  示例 1:

输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。

这个题很微妙,也很神奇,这是一道简单题,但我怎么感觉这么难,有三种方法,暴力,函数妙手,kmp,kmp还没学会

方法1:暴力,用双指针法,每次都和第一组元素对比,而且还要满足另外一个条件,那就是输入字符串总数是第一组循环元素的倍数,不然就会出现半循环aabaabaa,aabaaba这样,也会输出true。很好,我成功将easy题变成medium题。

class Solution { //全错了,用双指针法,通过了
public:
    bool repeatedSubstringPattern(string s) { 
        int slow = 0, fast = 1;
        int fak = 0;
        int tmpslow, tmpfast;
        while (fast < s.size() / 2 + 1) {
            if (s[0] != s[fast]) fast++;
            else {
                fak = 0, tmpslow = 0, tmpfast = fast;
                while (tmpfast < s.size()) {
                    if (s[tmpslow % fast] != s[tmpfast]) {
                        fak = -1;
                        break;
                    }
                    tmpslow++, tmpfast++;
                }
                //if (fak != -1) return true;// 也不对 如果是半循环aabaabaa,aabaaba这样,也会输出true,该死
                // 下面这样就对了,暴力双指针
                if (fak != -1 && s.size() % fast == 0) return true;
                fast++;
            }
        }
        return false;
    }
};

方法2:两个元素拼一块,掐头去尾之后再find,现在看看,不多不说,这个方法好精妙啊,优雅,太优雅了

class Solution { //用STL查找算法,聪明
public:
    bool repeatedSubstringPattern(string s) {
        string t = s + s;
        t.erase(t.begin()); t.erase(t.end() - 1); // 掐头去尾
        if (t.find(s) != std::string::npos) return true; // 未找到,返回npos
        return false;
    }
};

方法3:kmp,不过kmp的方法还没有学会,等更新