代码随想录第九天 | Leecode 151.翻转字符串里的单词、Leecode 28. 找出字符串中第一个匹配项的下标、Leecode 459.重复的子字符串
Leecode 151.翻转字符串里的单词
题目链接:https://leetcode.cn/problems/reverse-words-in-a-string/description/
题目描述
给你一个字符串 s ,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
- 示例 1:
输入:
s = "the sky is blue"
输出:"blue is sky the"
- 示例 2:
输入:
s = " hello world "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。
- 示例 3:
输入:
s = "a good example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。
解法一 使用栈来进行翻转
本题要求反转字符串中的单词,同时需要将其中多个空格变成一个空格,并且删去最后处于字符串首尾的空格。
- 首先对于字符串中空格的处理,考虑使用一个指针来扫描遍历整个字符串;
- 如果遇到空格则需要判断一下这个空格的前一个位置是不是也是空格,就可以判断当前是否是连续多个空格的情况;
- 需要注意处理字符串首的空格的情况
- 其次考虑如何将字符串以单词为单位翻转:考虑使用一个栈,将字符串以单词为单位的子字符串入栈,最后再出栈进行拼接
- 使用一个指针来扫描字符串,遇到非空字符,则将其加到当前单词字符串末尾
- 如果遇到空格,且上一个位置是空格之外的字符,则将当前一段单词入栈
- 将字符串按照单词为单位分割入栈之后,再根据栈的“先进后出”的特点,依次出栈进行拼接,并注意使用空字符进行连接,即可得到最终的字符串
根据上面使用栈的思路可以得到代码如下所示:
class Solution {
public:
string reverseWords(string s) {
stack<string> strS; // 初始化一个空的用于存放字符串的栈
int i = 0; // 初始化遍历指针
string word = ""; // 初始化单词字符串
while(s[i] == ' '){i++;} // 处理字符串首连续空格的情况
for(;i < s.size();i++){ // 从头到尾遍历字符串
if(s[i] == ' ' && s[i-1] != ' ') { // 如果遇到空格,且前一个字符不为空格,说明刚遍历完一个单词
strS.push(word); // 则将当前记录的单词字符串入栈
word = ""; // 并清空重置当前单词字符串
}
if(s[i] != ' ')word += s[i]; // 如果当前字符非空格,则添加到单词字符串中
}
string result = word; // 直接用最后一个单词来初始化最后要输出的字符串
while(!strS.empty()){
if(result != "")result += ' '; // 增加判断可以排除当最后一个单词为空的情况,否则会使得最后输出结果的开头多一个空格
result += strS.top(); // 拼接输出的单词
strS.pop(); // 让单词出栈
}
return result; // 最后返回结果字符串
}
};
接下来再上面代码的时间复杂度,首先是将单词逐个压栈,这时候时间复杂度为\(O(n)\),随后再将每个单词字符串逐个出栈并拼接,这时最坏情况也只有\(\frac{n}{2}\)个单词,故时间复杂度也是不大于\(O(n)\),由于这两部分过程是先后执行,而非循环嵌套,因此时间复杂度相加,最终的时间复杂度为\(O(n)\)。同时考虑一下空间复杂度,其中使用了一个额外的字符串变量word和一个字符串栈strS,都是会随着输入的字符串长度的变化的,即空间复杂度为\(O(n)\)。
接下来我们再考虑另一种解法,将空间复杂度降为\(O(1)\)。
解法二 \(O(1)\) 空间复杂度的算法
首先是如何处理字符串中的空格。为了不使用另外一个字符串来存储已删除多余空格的字符串,我们考虑使用快慢指针的方法:
- 如果快指针指向为字符则将字符复制到慢指针处的字符,并在复制之前先在慢指针处加一个空格
- 如果快指针遇到空格则不进行操作
- 重新修改字符串长度
使用这样的方式,快指针遍历完整个字符串之后,即可完成整个字符串中多余空格的删除。我们此处可以先给出这个移除多余空格的函数:
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); // 重新更改字符串长度
}
在删去了字符串中所有多余空格后,我们再考虑如何不使用堆栈,如何能够将字符串中的单词翻转。其实只需要先将整个字符串翻转一遍,随后再将各个单词逐个翻转即可。而翻转字符串中一部分内容的操作我们在昨天就已经写过很多次。由此我们可以得到代码如下:
class Solution {
public:
void reverseStr(string& s, int start, int end){ // 翻转字符串中的一个子字符串
for(int i = 0; start + i < end - i; i++){
swap(s[start+i], s[end-i]);
}
}
string reverseWords(string s) {
removeExtraSpaces(s); // 先移除多余空格
reverseStr(s,0,s.size()-1); // 翻转整个字符串
int index = 0; // 每个单词的第一个序号
for(int i = 0; i < s.size(); i++){ // 遍历查找每个单词的最后一个序号
if(i == s.size()-1 || s[i+1] == ' '){ // 如果是字符串末尾,或是下一个位置为空格,说明是单词最后一位
reverseStr(s,index,i); // 翻转单词
index = i + 2; // 下一个单词的首字母就在上一个单词的尾字母+2(+1是空格)
}
}
return s;
}
};
使用上面的代码即可在仅使用\(O(1)\)空间复杂度完成本题。
Leecode 28. 找出字符串中第一个匹配项的下标
题目描述
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
- 示例 1:
输入:
haystack = "sadbutsad",needle = "sad"
输出:0
解释:"sad"在下标0和6处匹配。
第一个匹配项的下标是0,所以返回0。
- 示例 2:
输入:
haystack = "leetcode",needle = "leeto"
输出:-1
解释:"leeto"没有在"leetcode"中出现,所以返回-1。
解法1 使用双指针
考虑使用两个指针来对字符串进行遍历,其中一个指针i从头到尾扫过haystack字符串,另一个指针用来遍历字符串haystack中第i个字符开始且与字符串needle相等的部分。即可以这样理解:
- 当指针
i指向haystack中字符与needle中第一个字符不相等时,两个指针i和j相等,且一同往后遍历扫过字符串haystack - 当指针
i指向haystack中字符与needle中第一个字符相等时,指针i暂停,只有指针j继续往后遍历,同时逐个比较字符串needle中第j-i处的字符是否相等- 如果有一个字符不相等,则指针
j又返回i的位置,并两个指针同时+1继续往后遍历 - 如果遍历完
needle字符串都还没有出现不相同的字符,则说明已经找到了所需要的位置,则返回当前指针i
- 如果有一个字符不相等,则指针
根据上面算法思想,可以得到下面代码:
class Solution {
public:
int strStr(string haystack, string needle) {
if(needle.size() > haystack.size()) return -1; // 如果字符串needle更长,则直接返回-1;为了避免后续访问越界的情况
for(int i = 0; i < haystack.size() - needle.size() + 1; i++){ // 使用i遍历字符串haystack,如果后续字符长度小于needle长度,说明不存在,故也不需要继续搜索
for(int j = i; needle[j-i] == haystack[j] ; j++){ // 注意初始时j=i;如果当前haystack[i]处字符和needle[0]相等,则暂停i,使用j来遍历比较是否相等
if(j-i == needle.size()-1) return i; // 如果遍历完了字符串needle,说明此时的i就是要找的下标,直接返回即可
}
}
return -1; // 如果遍历完了整个字符串都没找到,则返回-1
}
};
现在分析上面算法的时间复杂度,第一个for循环是遍历数组haystack,最多需要遍历次数haystack.size() - needle.size()次,而第二个for循环相当于比较遍历数组needle,最多也就遍历needle.size()次;总用时大致上为\((m-n)n\)数量级,估计时间复杂度为\(O(m * n)\),其中\(m\)和\(n\)表示两个字符串的长度。
解法2 KMP算法
先挖个坑明天学完KMP再来写,今天休息一下
Leecode 459.重复的子字符串
题目描述
给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。
- 示例 1:
输入:
s = "abab"
输出:true
解释: 可由子串"ab"重复两次构成。
- 示例 2:
输入:
s = "aba"
输出:false
- 示例 3:
输入:
s = "abcabcabcabc"
输出:true
解释: 可由子串"abc"重复四次构成。 (或子串"abcabc"重复两次构成。)
解法1 暴力法求解
要匹配子字符串,考虑遍历可能取到的子字符串长度,长度可以取到从1到字符串的一半,每个长度为i的子字符串(即字符串的前i位字符)逐位与字符串中位置j%i的字符进行比较,如果遍历完整个字符串,同时此时恰好比较完子字符串的最后一位,即j%i==0,就说明该子字符串可以构成整个字符串。因此得到代码如下:
class Solution {
public:
bool repeatedSubstringPattern(string s) {
for(int i = 1; i <= s.size()/2; i++){ // 子字符串长度
int j = 0;
for(; s[j] == s[j%i]; j++){} // 子字符串每次比对的起始位置
if ( j >= s.size() && j%i == 0) return true;
}
return false;
}
};
上面代码的时间复杂度为\(O(n^2)\),因为外层遍历子字符串的长度是从1到\(\frac{n}{2}\),内层遍历需要逐位比较字符串,次数也是在\(O(n)\)数量级。因此两层\(O(n)\)的循环嵌套之后的时间复杂度就为\(O(n^2)\)。
今日总结
今天刷了三道字符串的题,剩下时间主要是在学课内面向对象的知识。学习了内联函数、名空间、深拷贝。刷题涉及到KMP算法准备明天再看,用KMP把这两题的解法2补上。
代码随想录第九天 | Leecode 151.翻转字符串里的单词、Leecode 28. 找出字符串中第一个匹配项的下标、Leecode 459.重复的子字符串的更多相关文章
- LeetCode 459. 重复的子字符串(Repeated Substring Pattern)
459. 重复的子字符串 459. Repeated Substring Pattern 题目描述 给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成.给定的字符串只含有小写英文字母,并且 ...
- Java实现 LeetCode 459 重复的子字符串
459. 重复的子字符串 给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成.给定的字符串只含有小写英文字母,并且长度不超过10000. 示例 1: 输入: "abab" ...
- Leetcode 459.重复的子字符串
重复的子字符串 给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成.给定的字符串只含有小写英文字母,并且长度不超过10000. 示例 1: 输入: "abab" 输出: ...
- 代码随想录算法训练营day08 | leetcode 344.反转字符串/541. 反转字符串II / 剑指Offer05.替换空格/151.翻转字符串里的单词/剑指Offer58-II.左旋转字符串
基础知识 // String -> char[] char[] string=s.toCharArray(); // char[] -> String String.valueOf(str ...
- C#版(击败100.00%的提交) - Leetcode 151. 翻转字符串里的单词 - 题解
版权声明: 本文为博主Bravo Yeung(知乎UserName同名)的原创文章,欲转载请先私信获博主允许,转载时请附上网址 http://blog.csdn.net/lzuacm. C#版 - L ...
- LeetCode 151. 翻转字符串里的单词(Reverse Words in a String)
151. 翻转字符串里的单词 151. Reverse Words in a String
- Java实现 LeetCode 151 翻转字符串里的单词
151. 翻转字符串里的单词 给定一个字符串,逐个翻转字符串中的每个单词. 示例 1: 输入: "the sky is blue" 输出: "blue is sky th ...
- 【LeetCode】151. 翻转字符串里的单词(剑指offer 58-I)
151. 翻转字符串里的单词 知识点:字符串:双指针 题目描述 给你一个字符串 s ,逐个翻转字符串中的所有 单词 . 单词 是由非空格字符组成的字符串.s 中使用至少一个空格将字符串中的 单词 分隔 ...
- 【算法训练营day8】LeetCode344. 反转字符串 LeetCode541. 反转字符串II 剑指Offer05. 替换空格 LeetCode151. 翻转字符串里的单词 剑指Offer58-II. 左旋转字符串
[算法训练营day8]LeetCode344. 反转字符串 LeetCode541. 反转字符串II 剑指Offer05. 替换空格 LeetCode151. 翻转字符串里的单词 剑指Offer58- ...
- leetcode python翻转字符串里的单词
# Leetcode 151 翻转字符串里的单词### 题目描述给定一个字符串,逐个翻转字符串中的每个单词. **示例1:** 输入: "the sky is blue" 输出: ...
随机推荐
- 使用-数据湖Iceberg和现有hive数仓打通并使用
一.集群配置 1.版本使用 技术 版本 iceberg 1.3.1 flink 1.16.1 spark 3.2.1 hive 2.3.7 dlc-presto 待定 2.集群配置调整 (1)使用hi ...
- 使用必读-使用Iceberg数据湖需要注意的点
一.开发注意事项 1.Iceberg选择合适的表版本 简述:Iceberg目前有两个表版本(V1和V2),根据数据选择合适的表版本. V1表只支持增量数据插入,适合做纯增量写入场景,如埋点数据. V2 ...
- 浅说 c++20 cppcoro (三)
浅说 c++20 cppcoro (三),https://www.cnblogs.com/bbqzsl/p/18679860 接着上一篇浅说 c++20 coroutine (二) ,继续没说完的事. ...
- 乌龟冬眠箱湿度监控系统和AI辅助建议功能的实现
家里小朋友养了一只小乌龟,到了冬天就冬眠了,早早地准备了一个冬眠箱,铺上椰土,在室温低于15℃时,就把小乌龟放到冬眠箱里,不一会儿它就自己钻入土中把自己藏了起来.按照惯例,需要每隔一定时间,对冬眠箱进 ...
- min-max 容斥(最值反演)学习笔记
min-max 容斥,又名最值反演(我其实更喜欢后面这个名字),是一种常用的反演思想. 引入 在皇后游戏一题中,我们曾经证明过 \(\max(a,b)-a-b=-\min(a,b)\). 我们尝试推广 ...
- nginx 如何强制跳转 https
本项目 nginx 作为代理服务 项目上线,客户说要加个安全证书 ,于是安全证书是加上了,可是htttp和https都能访问网站,客户要求不行必须强制用带有https的地址访问 开整 这是 http ...
- QT5笔记:24. 自定义对话框以及模态 调用
创建窗口时 窗口对象为QDialog 调用方法为exec(); int res = setSizeDialog->exec();//模态窗口 (不必要)exec可以获取到调用的是对话框的 QDi ...
- Linux - 安装centos7.x之后发现ping不了www.baidu.com
1.首先检查网络配置 /etc/sysconfig/network-scripts 下的网卡配置文件是否有误 检查方向:IP.网关.子网掩码.DNS.ONBOOT参数 2.检查防火墙是否关闭 # 查看 ...
- 公众号已上线 Ask AI 功能
Get新技能,给公众号接入AI智能体,没花一分钱. 不禁感慨这时代的进步也太快了,曾经科幻小说中描绘的未来已成现实! 下面是笔者在腾讯元宝中创建的智能体"鲸鱼小助手": 如果今后要 ...
- Ubuntu修改密码和用户名
Ubuntu是一个Linux操作系统,修改密码和用户名是有危险的动作,请谨慎修改. 一.Ubuntu修改密码和用户名 Ubuntu更改密码步骤:1.进入Ubuntu,打开一个终端,输入 sudo su ...