力扣日记7:字符串

目录

344.反转字符串

541.反转字符串Ⅱ

剑指Offer 05.替换空格

151.反转字符串中的单词

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

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

459. 重复的子字符串


注意:

如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数。

如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。

344.反转字符串

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

541.反转字符串Ⅱ

  • 当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。
class Solution {
public:
    string reverseStr(string s, int k) {
        for (int i = 0; i < s.size(); i += 2 * k) {
            if (i + k <= s.size()) {
                reverse(s.begin() + i, s.begin() + i + k );
            } else {
                reverse(s.begin() + i, s.end());
            }
        }
        return s;
    }
};

剑指Offer 05.替换空格

  • 最开始的想法:用一个新字符串一个个重新添加
class Solution {
public:
    string replaceSpace(string s) {
        string res = "";
        for (int i = 0; i < s.length(); i++) {
            if (s[i] == ' ') {
                res += "%20";
            } else {
                res += s[i];
            }
        }
        return res;
    }
};
  • 后续优化:不使用额外空间
  • 先根据空格数扩充字符串,再利用双指针将字符串元素后移
class Solution {
public:
    string replaceSpace(string s) {
        int cnt = 0, oldSize = s.size();
        for (int i = 0; i < s.length(); i++) {
            if (s[i] == ' ') cnt++;
        }
        s.resize(s.size() + cnt * 2);
        int newSize = s.size();
        for (int i = oldSize - 1, j = newSize - 1; i >= 0; i--, j--) {
            if (s[i] != ' ') s[j] = s[i];
            else {
                s[j] = '0';
                s[j - 1] = '2';
                s[j - 2] = '%';
                j -= 2; 
            }
        }
        return s;
    }
};

151.反转字符串中的单词

  • 综合考察了字符串的多种操作
  • 一种方法是使用额外的辅助空间
  • 还有一种方法是只在原字符串上操作
  1. 移除多余空格
  2. 将字符串反转
  3. 根据空格将每个单词反转

移除空格上可以使用双指针法

void removeExtraSpaces(string& s) {
        int slow = 0, fast = 0;
        // 移除前缀空格
        while (fast < s.size() && s[fast] == ' ') fast++;
        for (; fast < s.size(); fast++) {
            // 移除中间重复空格
            if (s[fast] == ' ' && s[fast] == s[fast - 1]) continue;
            else s[slow++] = s[fast];
        }
        // 对末尾是空格的情况进行处理
        if (s[slow - 1] == ' ') s.resize(slow - 1);
        else s.resize(slow);
    }

简化版(有点难理解)

在遇到空格时,将空格后的单词全部向后移动

void removeExtraSpaces(string& s) {
        int slow = 0;
        for (int fast = 0; fast < s.size(); fast++) {
            if (s[fast] != ' ') {
                if (slow != 0) {
                    s[slow++] = ' ';
                }
                while (fast < s.size() && s[fast] != ' ') {
                    s[slow++] = s[fast++];
                }
            }
        }
        s.resize(slow);
    }

整体代码

class Solution {
public:
    void removeExtraSpaces(string& s) {
        int slow = 0;
        for (int fast = 0; fast < s.size(); fast++) {
            if (s[fast] != ' ') {
                if (slow != 0) {
                    s[slow++] = ' ';
                }
                while (fast < s.size() && s[fast] != ' ') {
                    s[slow++] = s[fast++];
                }
            }
        }
        s.resize(slow);
    }
    string reverseWords(string s) {
        removeExtraSpaces(s);
        reverse(s.begin(), s.begin() + s.size());
        int start = 0;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ' ') {
                // 找到空格后,将空格前的单词反转
                reverse(s.begin() + start, s.begin() + i);
                start = i + 1;
            } else if (i == s.size() - 1) {
                // 反转最后一个单词
                reverse(s.begin() + start, s.end());
            }
        }
        return s;
    }
};

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

  • 很自然就就会想到用切片substr做

        获取str[n:] 和 str[:n]子串,目标串target = str[:n] + str[n:];

  • 自己的思路(双指针)
class Solution {
public:
    string reverseLeftWords(string s, int n) {
        int nn = s.size();
        s.resize(s.size() + n);
        for (int i = 0, j = nn; i < n || j < s.size(); i++, j++) {
            s[j] = s[i];
        }  
        for (int i = 0, j = n; i < nn; i++,j++) {
            s[i] = s[j];
        }
        s.resize(nn);
        return s;
    }
};
  • 还有一个很巧妙的解法,通过局部反转+整体反转实现
  1. 反转区间为前n的子串
  2. 反转区间为n到末尾的子串
  3. 反转整个字符串

  • 代码也巨简洁
class Solution {
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. 找出字符串中第一个匹配项的下标

  • KMP环节
  • i是后缀末尾,j是前缀末尾,也是包括i之前的子串最长相等前后缀的长度
  • 一种写法是让前缀表统一减1,这样回退 j = next[j]
class Solution {
public:
    void getNext(int *next, string &s) {
        int j = -1;
        next[0] = j;
        for (int i = 1; i < s.size(); i++) {
            while (j >= 0 && s[i] != s[j + 1]) j = next[j];
            if (s[i] == s[j + 1]) j++;
            next[i] = j;
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) return 0;
        int next[needle.size()];
        getNext(next, needle);
        int j = -1;
        for (int i = 0; i < haystack.size(); i++) {
            while (j >= 0 && haystack[i] != needle[j + 1]) j = next[j];
            if (haystack[i] == needle[j + 1]) j++;
            if (j == (needle.size() - 1)) return i - needle.size() + 1;
        }
        return -1;
    }
};
  • 另一种不减1,回退 j = next[j - 1]
class Solution {
public:
    void getNext(int *next, string &s) {
        int j = 0;
        next[0] = j;
        for (int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) j = next[j - 1];
            if (s[i] == s[j]) j++;
            next[i] = j;
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) return 0;
        int next[needle.size()];
        getNext(next, needle);
        int j = 0;
        for (int i = 0; i < haystack.size(); i++) {
            while (j > 0 && haystack[i] != needle[j]) j = next[j - 1];
            if (haystack[i] == needle[j]) j++;
            if (j == needle.size()) return i - needle.size() + 1;
        }
        return -1;
    }
};

459. 重复的子字符串

  • 移动匹配解法:当s为重复字符串时, s + s组成的字符串中,后面的子串做前串,前后的子串做后串,就一定还能组成一个s。
  • 我们将头尾元素删除后在整个字符串中查找s,即可得到结果

class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        string ss = s + s;
        ss.erase(ss.begin());
        ss.erase(ss.end() - 1); // 去头尾元素
        if (ss.find(s) != string::npos) {
            return true;
        }   
        return false;
    }
};

  • KMP解法:若字符串是重复的,那么利用next数组就可以匹配到最长公共子串

next[len - 1] = 7,next[len - 1] + 1 = 8,8就是此时字符串asdfasdfasdf的最长相同前后缀的长度。

(len - (next[len - 1] + 1)) 也就是: 12(字符串的长度) - 8(最长公共前后缀的长度) = 4, 4正好可以被 12(字符串的长度) 整除,所以说明有重复的子字符串(asdf)。

class Solution {
public:
    void getNext(int *next, string &s) {
        int j = -1;
        next[0] = j;
        for (int i = 1; i < s.size(); i++) {
            while (j >= 0 && s[i] != s[j + 1]) j = next[j];
            if (s[i] == s[j + 1]) j++;
            next[i] = j;
        }
    }
    bool repeatedSubstringPattern(string s) {
        int next[s.size()];
        getNext(next, s);
        int l = s.size();
        if (next[l - 1] != -1 && l % (l - next[l - 1] - 1) == 0) {
            return true;
        }
        return false;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值