代码随想录算法训练营第8天|字符串part02

第四章 字符串part02

今日任务

  • 151.翻转字符串里的单词
  • 卡码网:55.右旋转字符串
  • 28. 实现 strStr()
  • 459.重复的子字符串
  • 字符串总结
  • 双指针回顾

详细布置

151.翻转字符串里的单词

建议:这道题目基本把 刚刚做过的字符串操作 都覆盖了,不过就算知道解题思路,本题代码并不容易写,要多练一练。

题目链接/文章讲解/视频讲解:代码随想录

所以解题思路如下:

  • 移除多余空格
  • 将整个字符串反转
  • 将每个单词反转

举个例子,源字符串为:"the sky is blue "

  • 移除多余空格 : "the sky is blue"
  • 字符串反转:"eulb si yks eht"
  • 单词反转:"blue is sky the"

这样我们就完成了翻转字符串里的单词。

感觉算法的思路怎么这么巧妙呢

由于不知道开头中间结尾的空格究竟有多少个,所以去除空格确实有点麻烦,针对快慢指针的题目,完全可以把它想象成两个空间,一个输入字符串,一个输出字符串。快指针就一直遍历,慢指针用来誊抄。先看 if(s[i]==" ") if (slow != 0) s[slow++] = ' ';这行代码。这行代码是处理假如开头就空格的情况,slow是0,不满足条件,i++,直到快指针没遇到空格。然后if (slow != 0) 这行代码就没必要看了,因为已经解决开头是空格的情况,slow不为0。快指针遇到了字母后,慢指针抄下来。遇到空格以后,慢指针就不抄了,快指针i继续不断++,直到遇到单词,s[slow++] = ' ';即:s[slow] = ' ';slow++;slow抄写空格,然后继续++。这样就把全部抄完了。假如最后几个空格,i一直++遍历完,都是空格结束,slow就不抄了。结束。最后别忘了重新设置下字符串大小呢。

class Solution {
public:
void  deleteSpace(string &s)
{   
    int slow =0;
   for(int i=0;i<s.size();i++) 
   {
    if(s[i]!=' ')
    {
          if (slow != 0) s[slow++] = ' '; 
          while(s[i]!=' '&&i<s.size())
          s[slow++]=s[i++];
    }  
   }
    s.resize(slow);    
}
  void  reverseWord (string &s,int start,int end)
{   
    while(start<end)
    {
    s[start]^=s[end];
    s[end]^=s[start];
    s[start]^=s[end];
    start++;
    end--;
    }
}
void  reverseAll(string &s)
{   
    reverseWord (s,0,s.size()-1);
}
    string reverseWords(string s) {
        deleteSpace(s);
        reverseAll(s);
        int start = 0, end = 0;
        for(int i=0;i<s.size();i++) 
        {
            if(s[i]==' ')
            {
                end=i-1;
                reverseWord (s,start,end);
                start=i+1;
            }     
        }
        reverseWord (s,start,s.size()-1);
        return s;
            }
};

s[i] == " " 在这行代码中,s[i] 是一个字符 (char),而 " " 是一个 C 风格的字符串(实际上是一个指向字符串常量的指针 const char*)。因此,你是在比较一个字符和一个字符串指针,这是不合法的。

卡码网:55.右旋转字符串

 

建议:题解中的解法如果没接触过的话,应该会想不到

 

题目链接/文章讲解:

代码随想录

为了让本题更有意义,提升一下本题难度:不能申请额外空间,只能在本串上操作。 (Java不能在字符串上修改,所以使用java一定要开辟新空间)

其实

123456789
123456789
123456789
987654321
891234567

其实讲了那么多,还是有点华丽呼哨,还是先看以上表格吧,假设“123456789”字符串移动两个位置,第二行,就是移动的结果,此时,就可以根据第三行,把字符分成两部分,part1和part2,咱们可以先来个全局转换,part1和part2调换下顺序,但是此时有个副作用,按理说part1和part2内部也调换了位置,因此再把内部负负得正就解决问题了。

整体代码如下:

#include<iostream>
#include<algorithm>
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); // 先反转前一段,长度n
    reverse(s.begin() + n, s.end()); // 再反转后一段

    cout << s << endl;

} 

28. 实现 strStr() (本题可以跳过)

 

KMP算法确实太难了,当时花了两三天才搞明白,果然在这种复杂题上不要过分钻牛角尖,当时还不知道双指针,前缀后缀的思想,确实很头秃,现在才试着研究下。

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。

示例 1: 输入: haystack = "hello", needle = "ll" 输出: 2

示例 2: 输入: haystack = "aaaaa", needle = "bba" 输出: -1

题目链接/文章讲解/视频讲解:代码随想录

首先我们要知道说明是前缀,什么是后缀。比如说"abcab","a","ab","abc","abca"是"abab"的前缀,注意"abcab"不是前缀,同理"b","ab"“cab”"bcab"是"abcab"的后缀,注意“abcab”不是后缀。通过这个例子就懂什么是前后缀。

然后要求对于一个字符串,公共最大前后缀。我们依次看“a”,由于前后缀不能是自身,所以根本没有前后缀,为0;“ab,前缀只有“a”,后缀只有b,没有公共前后缀,所以也为0;“abc”,没有公共前后缀,所以也为0;“abca”,有公共前后缀"a",所以为1;abcab有公共前后缀“ab”,所以为2

为什么求公共最大前后缀呢。大家想想,如果暴力法,在比对的时候,前面几个字符都匹配成功,但是最后的b和一个匹配失败的文本“X”对比,匹配失败。按照正常流程,就要从needle[0]重新开始匹配,a和b匹配失败,下一个,a和c匹配失败,再下一个,a和a匹配成功,很好,继续,但是问题是,我们知道匹配不成功的X前面是abca,我完全可以一步到位,直接跳到第三步。继续比较b和X。当一段文本大量存在“abcaX”的时候,这种方法就很方便。公共最大子前后缀,就是确定我应该跳到哪一步。所以我们的第一步就是求前后缀。

12345abcaX?????
abcab
abcab
abcab
abca
abca
012345678910
ababcdababa
00120012343
-1-101-1-101232
ababcd

为什么要减一,因为为了和数组对应上,而且当为-1时也好进行判断,确定进入下一个循环。

 j = next[j];毫无疑问是最难理解的,具体可以看上表,假设字符串"ababcdababa";只考虑最长子字符串。到了s[9]时,"abab"和"abab"前后缀相同,所以最大公共前后缀为4,再减去1为3。然后到s[10],第十位没有匹配上,j=next[j];j=next[3]=1,然后进行判断 if (s[i] == s[j ]+1),'a'=='a',j++,j=next[10]=2。

我们可以考虑,s[10]没有匹配上,当前9个字符的子字符串最大公共前后缀子字符串为3,说明只可能在"abab"中找最大前缀,我们要回退到"abab"中找新的前10个字符的子字符串。这时候我们知道前面s[0]-s[3]和s[6]-s[9]匹配上,那么我们就考虑可不可以,把它们剪切掉,对比小份的,这时候就要回退到next[j],s[3]对应的前缀"ab"和后缀"ab"相同,而ab也是s[8]的后缀,又一个相同,所以ab是原本第二小的公共前后缀,这时候我们再进行判断,if (s[i] == s[j + 1]),此时s[2]和s[10]相同,此时公共最大子字符串为3,next[10]=3-1=2;
通过上面的例子,我们发现代码还可以继续优化,因为我们发现我们可以求nextVal[],

在数值传递的时候&next[0]对应着int* next,这是一个指向next的指针,vector<int>& next对应着next,这是一个引用。

感觉是自己做过最难的题,花了两三天真™心烦,至少明白了为什么要回退,为什么最大前后缀子字符串为什么要减一,为什么比较的时候要+1,因为是最大前后缀子字符串之后的一个字符进行比较。另外,一定要先while后if,因为判断是最本质的需求,所以放在最后,否则前面可能运行了,影响后面的判断。

class Solution {
public:
void getNext(int* next, const 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[j+1]==s[i])
            j++;
        next[i]=j;
        }     
    }

    int strStr(string haystack, string needle) 
    {
        vector<int> next(needle.size());
        getNext(&next[0],needle);
        int j=-1;
        for(int i=0;i<haystack.size();i++)
        {
            while (haystack[i]!=needle[j+1]&&j>=0)//j+1是因为最大公共前后缀子字符串,然后要继续加1
            {
                j=next[j];  
            }
            if(haystack[i]==needle[j+1])
            {
                j++;
                if(j==needle.size()-1)
                {
                return i-j;
                }
                continue;
            }    
        }
        return -1;       
    }
};

字符串总结

比较简单,大家读一遍就行

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

双指针回顾

此时我们已经做过10道双指针的题目了,来一起回顾一下,大家自己也总结一下双指针的心得

文章讲解:代码随想录

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值