第四章 字符串part02

翻转字符串中的单词

题目描述

给定一个字符串,逐个翻转字符串中的每个单词。

示例 1:
输入: “the sky is blue”
输出: “blue is sky the”

示例 2:
输入: " hello world! "
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

示例 3:
输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

解题思路

总体思路

reverse函数:

  • 用于反转字符串中从 startend 区间的字符。
  • 使用两个指针,一个从区间的开始 (start),另一个从区间的结束 (end),向中间移动。
  • 在移动过程中,交换两个指针指向的字符,直到两个指针相遇。

removeExtraSpaces函数

  • 用于移除字符串中的多余空格,只保留单词之间的单个空格。
  • 使用两个指针:slowislow 指针用于记录有效字符的位置,i 指针用于遍历字符串。
  • 当遇到非空格字符时,如果 slow 不是0(即不是字符串的第一个字符),则在 slow 位置添加一个空格,然后复制所有连续的非空格字符到 slow 指针之后。
  • 如果遇到空格字符,则跳过,除非它是单词之间的唯一空格。
  • 最后,调整字符串的大小以移除末尾的空格。

reverseWord函数

  • 调用 removeExtraSpaces 函数来清理字符串中的多余空格。
  • 然后,调用 reverse 函数将整个字符串反转,这样原本在字符串末尾的单词会移动到字符串的开头。
  • 接着,遍历字符串,当遇到空格或到达字符串末尾时,调用 reverse 函数将当前单词反转,这样每个单词内的字符顺序就恢复了正常。
  • 这个过程确保了单词的顺序被反转,但每个单词内的字符顺序保持不变。
  • 最后,返回处理后的字符串。

时空分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(1) 或 O(n),取决于语言中字符串是否可变

代码实现

测试地址:https://leetcode.cn/problems/reverse-words-in-a-string/description/

class Solution {
public:
    // 反转字符串中指定区间的字符
    void reverse(string& s, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            swap(s[i], s[j]);
        }
    }

    // 移除字符串中的多余空格,只保留单词间的单个空格
    void removeExtraSpaces(string& s) {
        int slow = 0; // 慢指针,用于记录有效字符的位置
        for (int i = 0; i < s.size(); i++) {
            if (s[i] != ' ') { // 遇到非空格字符
                if (slow != 0) // 如果不是第一个字符,添加一个空格
                    s[slow++] = ' ';
                while (i < s.size() && s[i] != ' ') { // 复制非空格字符
                    s[slow++] = s[i++];
                }
            }
        }
        s.resize(slow); // 调整字符串大小以移除末尾的空格
    }

    // 反转字符串中的单词顺序,同时保持单词内的字符顺序不变
    string reverseWords(string s) {
        removeExtraSpaces(s); // 移除多余空格
        reverse(s, 0, s.size() - 1); // 反转整个字符串
        int start = 0; // 记录每个单词的起始位置
        for (int i = 0; i <= s.size(); i++) {
            if (i == s.size() || s[i] == ' ') { // 遇到空格或字符串末尾
                reverse(s, start, i - 1); // 反转当前单词
                start = i + 1; // 移动到下一个单词的起始位置
            }
        }
        return s; // 返回处理后的字符串
    }
};

右旋字符串

题目描述

字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。

例如,对于输入字符串 “abcdefg” 和整数 2,函数应该将其转换为 “fgabcde”。

输入:输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。

输出:输出共一行,为进行了右旋转操作后的字符串。

样例输入:

2
abcdefg 

样例输出:

fgabcde

数据范围:1 <= k < 10000, 1 <= s.length < 10000;

解题思路

总体思路:

假设字符串为s,长度为len,可以根据n的值将字符串分为两个子字符串

方式一:

  • 先将字符串s整体反转
  • 将[0~n)子字符串进行反转
  • 将[len-n~s.end())子字符串反转

方式二:

  • 先将[0~len-n)子字符串进行反转
  • 将[0~len-n ~ s.end())子字符串反转
  • 将字符串s整体反转

代码实现

测试地址:https://kamacoder.com/problempage.php?pid=1065

方式一:

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n;
    string s;
    cin >> n;
    cin >> s;
    int len = s.size();
  
    reverse(s.begin(),s.end());
    reverse(s.begin(),s.begin() + n);
    reverse(s.begin() + n,s.end());
  
    cout << s << endl;

    return 0;
}

方式二:

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n;
    string s;
    cin >> n;
    cin >> s;
    int len = s.size();
  
    reverse(s.begin(),s.begin() + len - n);
    reverse(s.begin() + len - n,s.end());
    reverse(s.begin(),s.end());
  
    cout << s << endl;

    return 0;
}

实现strStr()

题目描述

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

解题思路

总体思路:

通过KMP算法生成next数组,在文本串 haystack 中查找模式串 needle 的起始位置。

  1. 如果 needle 为空,直接返回 0,因为空字符串显然在任何位置都是匹配的。

  2. 创建并初始化 next 数组,调用 getNext 函数为 needle 填充 next 数组。

  3. 使用变量 j 来追踪当前在模式串 needle 中的匹配位置。

  4. 遍历文本串 haystack 的每个字符:

    • 如果当前字符不匹配,并且 j 大于 0,则使用 next 数组回溯 j 的值,避免重复匹配。
    • 如果当前字符匹配,j1,表示模式串在当前位置匹配成功。
    • 如果 j 等于模式串 needle 的长度,说明在文本串 haystack 中找到了一个完整的 needle 匹配,返回当前匹配的起始位置 (i - needle.size() + 1)
  5. 如果完成遍历后没有找到匹配,返回 -1

时空分析:

  • 时间复杂度: O(n + m)
  • 空间复杂度: O(m)

代码实现

测试地址:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/

class Solution {
public:
    // 计算模式串 needle 的 next 数组,用于 KMP 算法
    void getNext(int* next, const string& s) {
        int j = 0; // 前缀的末尾
        next[0] = 0; // 初始化 next[0] 为 0
        for(int i = 1; i < s.size(); i++) {
            // 当当前字符不匹配时,回溯到前一个位置的 next 值
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }
            // 如果当前字符匹配,则前缀末尾后移
            if (s[i] == s[j]) {
                j++;
            }
            // 更新 next[i] 为当前前缀末尾的位置
            next[i] = j;
        }
    }

    // 在 haystack 中查找 needle 的起始位置,使用 KMP 算法
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0; // 如果 needle 为空,返回 0
        }
        vector<int> next(needle.size()); // 创建 next 数组
        getNext(&next[0], needle); // 计算 needle 的 next 数组
        int j = 0; // 模式串的当前位置
        for (int i = 0; i < haystack.size(); i++) {
            // 当字符不匹配时,根据 next 数组回溯
            while(j > 0 && haystack[i] != needle[j]) {
                j = next[j - 1];
            }
            // 如果字符匹配,模式串位置后移
            if (haystack[i] == needle[j]) {
                j++;
            }
            // 如果模式串完全匹配,返回其在 haystack 中的起始位置
            if (j == needle.size() ) {
                return (i - needle.size() + 1);
            }
        }
        return -1; // 如果未找到匹配,返回 -1
    }
};

重复的字符串

题目描述

给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。

示例 1:

  • 输入: “abab”
  • 输出: True
  • 解释: 可由子字符串 “ab” 重复两次构成。

示例 2:

  • 输入: “aba”
  • 输出: False

示例 3:

  • 输入: “abcabcabcabc”
  • 输出: True
  • 解释: 可由子字符串 “abc” 重复四次构成。 (或者子字符串 “abcabc” 重复两次构成。)

解题思路

移动匹配

总体思路

如果某个字符串 s 由重复的子字符串构成,那么将两个字符串s拼接生成字符串t,将字符串t掐头去尾后还能找到字符串s,说明字符串s为重复的子字符串。例如:

  1. 字符串s:abab,将两个字符串s进行拼接,生成字符串t:abababab
  2. 将字符串t进行掐头去尾得到:bababa
  3. 在字符串:bababa 中任然可以找到字符串s:abab,因此字符串s为重复字符串

时空分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

KMP算法

总体思路

基于KMP算法构建字符串s的前缀表,然后判断字符串是否能够整除(字符串长度-最长相等前后缀的长度),如果可以说明该字符串有重复的子字符串,这里以字符串:abcabcabc为例。

  1. 求出字符串:abcabcabc 的 前缀表为:000123456,可以得出最长相等前后缀的长度为6
  2. 将字符串长度-最长相等前后缀:9 - 6 = 3
  3. 将字符串长度 % (字符串长度-最长相等前后缀):9 % 3 = 0,说明可以被整除,说明字符串由重复子字符串构成

时空分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

代码实现

测试地址:https://leetcode.cn/problems/repeated-substring-pattern/description/

class Solution {
public:
    // 构建前缀表(next数组)
    void getNext(int* next, const string& s) {
        next[0] = 0;  // 前缀表的第一位设为0,因为单个字符没有前后缀
        int j = 0;    // 初始化j,j用于在字符串s中遍历前缀和后缀
        // 遍历字符串s以构建next数组
        for (int i = 1; i < s.size(); i++) {
            // 通过next数组回溯,直到找到一个可匹配的前缀或回溯到字符串开始位置
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }
            // 如果找到匹配的前后缀,则j增加1
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j; // 更新next数组
        }
    }

    //判断字符串s是否由重复的子字符串构成
    bool repeatedSubstringPattern(string s) {
        if (s.size() == 0) {  // 如果字符串为空,立即返回false
            return false;
        }
        int next[s.size()];  // 创建一个大小与s相同的next数组
        getNext(next, s);    // 构建s的next数组
        int len = s.size();  // 获取字符串长度
        // 检查字符串是否能由其最长相等前后缀以外的部分的整数倍构成
        // 也就是说字符串s的长度能否被(len - next[len - 1])整除
        if(next[len - 1] != 0 && len % (len - (next[len - 1])) == 0){
            return true; // 如果条件成立,说明字符串s可以由子字符串重复构成
        }
        return false; // 否则,说明不能由重复的子字符串构成
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值