代码随想录Day13-字符串:力扣第541、剑指Offer 05、151、剑指Offer 58、28题

541. 反转字符串II

题目链接
代码随想录文章讲解链接

方法一:模拟+双指针

用时:37m27s

思路

自己想的一个方法,其实想复杂了

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    string reverseStr(string s, int k) {
        int size = s.size();
        int left = 0;
        int right = 0;
        int count = 1;

        for (; count <= size; ++count) {
            if (count % (2 * k) == 0) {
                left = count - 2 * k;
                right = count - k - 1;
                while (left < right) swap(s[left++], s[right--]);
            }
        }
        --count;
        left = count - (count % (2 * k));
        right = min(left + k - 1, size - 1);
        while (left < right) swap(s[left++], s[right--]);
        return s;
    }
};

方法二:模拟

思路

遍历字符串,每次向前走2k步,翻转字符串i至i+k的字符,若i+k超出了字符串的范围,则把剩余的字符串全部翻转。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    string reverseStr(string s, int k) {
        int size = s.size();
        for (int i = 0; i < size; i += 2 * k) {
            if (i + k - 1 < size) reverse(s.begin() + i, s.begin() + i + k);
            else reverse(s.begin() + i, s.end());
        }
        return s;
    }
};

看完讲解的思考

一开始想复杂了,被题目带偏了,先往前走计数再回头交换位置。更简单的做法是先交换位置,再往前走。

代码实现遇到的问题

无。


剑指Offer 05. 替换空格

题目链接
代码随想录文章讲解链接

方法一:遍历

思路

最朴素的解法,新建一个字符串然后遍历赋值。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
    string replaceSpace(string s) {
        string res;
        for (char& c : s) {
            if (c != ' ') res += c;
            else res.append("%20");
        }
        return res;
    }
};

方法二:双指针原地修改

用时:8m9s

思路

首先遍历一遍字符串计算空格的数量,然后延长字符串,每有一个空格就新增两个字符。然后双指针从后往前赋值。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    string replaceSpace(string s) {
        int oriIdx = s.size() - 1;
        int count = 0;

        // 记录空格的数量
        for (char& c : s) {
            if (c == ' ') ++count;
        }
        // 每个空格需要新增两个字符
        while (count) {
            s += "  ";
            --count;
        }

        int newIdx = s.size() - 1;
        while (oriIdx >= 0) {
            if (s[oriIdx] != ' ') s[newIdx--] = s[oriIdx];
            else {
                s[newIdx--] = '0';
                s[newIdx--] = '2';
                s[newIdx--] = '%';
            }
            --oriIdx;
        }
        return s;
    }
};

看完讲解的思考

无。

代码实现遇到的问题

无。


151. 翻转字符串里的单词

题目链接
代码随想录文章讲解链接

方法一:模拟

思路

从后向前遍历,将单词放到一个新的字符串中。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
    string reverseWords(string s) {
        int size = s.size();
        int left = 0;
        int right = 1;

        // 翻转每一个单词,并且将空格翻转到字符串尾端
        while (right < size) {
            if (s[right] == ' ' && s[right - 1] != ' ') {
                reverse(s.begin() + left, s.begin() + right);
                while (s[left] != ' ' && left < right) ++left;
                ++left;
                ++right;
            } else ++right;
        }
        reverse(s.begin() + left, s.end());
        // 去除字符串尾部的空格
        while (s[s.size() - 1] == ' ') s.erase(s.size() - 1);
        // 整体翻转
        reverse(s.begin(), s.end());
        return s;
    }
};

方法二:原地修改-先局部翻转再全局翻转

用时:34m1s

思路

自己想的一个方法,把每一个单词以及空格局部翻转,通过多次局部翻转可以把多余的空格全部移动至字符串末端,然后将空格删除,最后再将整个字符串翻转。
思路有点类似于冒泡排序,多余的空格通过不断地翻转,位置逐渐右移直至末端。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    string reverseWords(string s) {
        int size = s.size();
        int left = 0;
        int right = 1;

        while (right < size) {
            if (s[right] == ' ' && s[right - 1] != ' ') {
                reverse(s.begin() + left, s.begin() + right);
                while (s[left] != ' ' && left < right) ++left;
                ++left;
                ++right;
            } else ++right;
        }
        reverse(s.begin() + left, s.end());
        while (s[s.size() - 1] == ' ') s.erase(s.size() - 1);
        reverse(s.begin(), s.end());
        return s;
    }
};

方法三:原地修改-先全局翻转再局部翻转

用时:4m29s

思路

先翻转整个字符串,然后再用双指针将每个单词每间隔一个空格赋值到字符串前端,再将该单词局部翻转。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    string reverseWords(string s) {
        int size = s.size();
        int write = 0;
        
        // 整体翻转
        reverse(s.begin(), s.end());
        // 将单词每空一格然后以至字符串前端并翻转
        for (int start = 0; start < size; ++start) {
            if (s[start] != ' ') {
            	// 每个单词间隔一个空格
                if (write != 0) s[write++] = ' ';
                // start为当前单词开始的位置,end为当前单词结束的位置
                int end = start;
                while (end < size && s[end] != ' ') s[write++] = s[end++];
                // 赋值到字符串前端后,再将单词翻转
                reverse(s.begin() + write - (end - start), s.begin() + write);
                start = end;
            }
        }
        // write之后的字符都删除掉
        s.erase(s.begin() + write, s.end());
        return s;
    }
};

看完讲解的思考

字符串局部翻转+全局翻转可以实现调整子串顺序的功能。
例如:abcd efgh
先分别翻转前四个字符和后四个字符:dcba hgfe
再整体翻转:efgh abcd
就能实现前一半子串和后一半子串顺序调换。

代码实现遇到的问题

无。


剑指offer 58. 左旋转字符串II

题目链接
代码随想录文章讲解链接

方法一:模拟

思路

新建一个字符串,然后按照规则得到给新的字符串赋值。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
    string reverseLeftWords(string s, int n) {
        int size = s.size();
        string res(s);
        
        for (int i = 0; i < n; ++i) res[size - n + i] = s[i];
        for (int i = n; i < size; ++i) res[i - n] = s[i];
        return res;
    }
};

方法二:原地修改

用时:1m19s

思路

先将前段字符串翻转,再将后段字符串翻转,最后将整个字符串翻转。

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
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. 找出字符串中第一个匹配项的下标

题目链接
代码随想录文章讲解链接

方法一:暴力解法

用时:8m12s

思路
  • 时间复杂度: O ( m ∗ n ) O(m*n) O(mn)
  • 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
class Solution {
public:
    int strStr(string haystack, string needle) {
        int size1 = haystack.size();
        int size2 = needle.size();

        for (int i = 0; i < size1; ++i) {
            if (haystack[i] == needle[0]) {
                bool flag = true;
                for (int j = 0; j < size2; ++j) {
                    if (haystack[i + j] != needle[j]) {
                        flag = false;
                        break;
                    }
                }
                if (flag) return i;
            }
        }
        return -1;
    }
};

方法二:KMP算法

用时:1h16m23s

思路
  1. next数组:当前位置之前的子串的最长相等前后缀长度。
    例如:ababaa

    index012345
    strababaa
    next-100123
    • next[0]表示str[0]之前的子串的最长相等前后缀长度,由于str[0]之前不存在字符串,所以设置为-1,即next[0] = -1。
    • next[1]表示str[1]之前的子串“a”的最长相等前后缀长度,由于“a”只有一个字符,没有前后缀,所以最长相等前后缀长度为0, 即next[1] = 0。
    • next[2]表示str[2]之前的子串“ab”的最长相等前后缀长度,“ab”最长相等前后缀长度为0, 即next[2] = 0。
    • next[3]表示str[3]之前的子串“aba”的最长相等前后缀长度,“aba”最长相等前后缀长度为1(前缀“a”和后缀“a”), 即next[3] = 1。
    • next[4]表示str[4]之前的子串“abab”的最长相等前后缀长度,“abab”最长相等前后缀长度为2(前缀“ab”和后缀“ab”), 即next[4] = 2。
    • next[5]表示str[5]之前的子串“ababa”的最长相等前后缀长度,“ababa”最长相等前后缀长度为3(前缀“aba”和后缀“aba”), 即next[5] = 3。
  2. 得到next数组后,遍历主串中的字符和模式串中的字符进行比对,若不满足则根据next数组回退到上一个要比对的地方,直至模式串开头。

  • 时间复杂度: O ( m + n ) O(m+n) O(m+n),m是主串的长度,n是模式串的长度。
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
    int strStr(string haystack, string needle) {
        int size1 = haystack.size();
        int size2 = needle.size();
        if (size2 == 0) return 0;
        vector<int> next = getNext(needle);
        int i = 0;
        int j = 0;

        while (i < size1 && j < size2) {
            if (haystack[i] == needle[j]) {
                ++i;
                ++j;
            } else {
                j = next[j];
                if (j == -1) {
                    j = 0;
                    ++i;
                }
            }
        }
        return j >= size2 ? i - size2 : -1;
    }

    vector<int> getNext(string& s) {
        int size = s.size();
        if (size == 0) return {};
        else if (size == 1) return {-1};
        else if (size == 2) return {-1, 0};
        vector<int> next(size, 0);
        next[0] = -1;
        next[1] = 0;

        for (int i = 2; i < size; ++i) {
            if (s[i - 1] == s[next[i - 1]]) next[i] = next[i - 1] + 1;
            else {
                int cur = next[next[i - 1]];
                while (cur != -1 && s[i - 1] != s[cur]) cur = next[cur];
                next[i] = cur + 1;
            }
        }
        return next;
    }
};

方法三:KMP算法优化

用时:

思路
  1. nextVal数组:next数组的优化版本。在匹配的过程中,当某个位置的字符不匹配时,根据next数组回退到另外一个位置的字符,例如str[2]不匹配,根据next[2]回退到str[0],但是str[2]和str[0]是相等的,如果str[2]不匹配则str[0]也是不匹配的,此处可以进行优化。
    str[i] == str[next[i]]时,nextVal[i] = next[next[i]]
    例如:ababaa

    index012345
    strababaa
    next-100123
    nextVal-10-10-13
  2. 得到nextVal数组后,遍历主串中的字符和模式串中的字符进行比对,若不满足则根据next数组回退到上一个要比对的地方,直至模式串开头。

  • 时间复杂度: O ( m + n ) O(m+n) O(m+n),m是主串的长度,n是模式串的长度。
  • 空间复杂度: O ( n ) O(n) O(n)
C++代码
class Solution {
public:
    int strStr(string haystack, string needle) {
        int size1 = haystack.size();
        int size2 = needle.size();
        if (size2 == 0) return 0;
        vector<int> next = getNextVal(needle);
        int i = 0;
        int j = 0;

        while (i < size1 && j < size2) {
            if (haystack[i] == needle[j]) {
                ++i;
                ++j;
            } else {
                j = next[j];
                if (j == -1) {
                    j = 0;
                    ++i;
                }
            }
        }
        return j >= size2 ? i - size2 : -1;
    }

    vector<int> getNext(string& s) {
        int size = s.size();
        if (size == 0) return {};
        else if (size == 1) return {-1};
        else if (size == 2) return {-1, 0};
        vector<int> next(size, 0);
        next[0] = -1;
        next[1] = 0;

        for (int i = 2; i < size; ++i) {
            if (s[i - 1] == s[next[i - 1]]) next[i] = next[i - 1] + 1;
            else {
                int cur = next[next[i - 1]];
                while (cur != -1 && s[i - 1] != s[cur]) cur = next[cur];
                next[i] = cur + 1;
            }
        }
        return next;
    }

    vector<int> getNextVal(string& s) {
        vector<int> next = getNext(s);
        int size = s.size();
        for (int i = 0; i < size; ++i) {
            if (next[i] != -1 && s[i] == s[next[i]]) {
                next[i] = next[next[i]];
            }
        }
        return next;
    }
};

看完讲解的思考

KMP算法好难啊,之后得多敲几遍。

代码实现遇到的问题

无。


最后的碎碎念

前段时间放假去了,断断续续的刷了几题,还都没完全刷完,今天把之前的坑填了填。KMP算法花了好多时间,虽然之前学过了,但是过一段时间又忘了,看来还是需要多敲几遍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值