代码随想录算法训练营第八天 |LeetCode344.反转字符串、541.反转字符串 II、剑指 Offer05.替换空格、LeetCode151.反转字符串中的单词、剑指 Offer=58-II.左旋转字符串
2023年2月25日
补博客的同时再做一遍
344. 反转字符串
题目链接:344. 反转字符串
思路:
- 双指针遍历交换
时间复杂度:O(n)
,空间复杂度O(1)
代码:
class Solution {
public:
void reverseString(vector<char>& s) {
int left = 0;
int right = s.size()-1;
while(left < right)
{
std::swap(s[left],s[right]);
++left;
--right;
}
}
};
总结:
- 比较简单
- 做题体验:
哈哈
(哈哈
>嘿嘿
>哎哎
>嘤嘤
) - 时间:
未计
一刷
541. 反转字符串 II
题目链接:541. 反转字符串 II
思路:
- 每次反转的起始位置是2k * n(n = 0,1,2,3……)
- 每次反转的结束位置是起始位置之后的k-i位,或者是字符串末尾
- 有这些信息,剩余的工作就和反转字符串一样了
时间复杂度:O(n)
,空间复杂度O(1)
代码:
class Solution {
public:
string reverseStr(string s, int k) {
for(int i = 0;i<s.size();i += 2*k)
{
int length = s.size();
int begin = i;
auto end = min(i+ k -1,length-1);
while(begin < end)
{
std::swap(s[begin],s[end]);
++begin;
--end;
}
}
return s;
}
};
总结:
- 一开始一直没搞清结构,做得不太理想,关键在于
for(int i = 0;i<s.size();i += 2*k)
这一步,想通了就豁然开朗了 - 做题体验:
哎哎
(哈哈
>嘿嘿
>哎哎
>嘤嘤
) - 时间:
未计
一刷
剑指 Offer 05. 替换空格
题目链接:剑指 Offer 05. 替换空格
思路:
- 简单的做法是直接遍历字符串,不是空格的话插入新字符串,是空格的话填入所需字符串
- carl哥提供了一种空间复杂度更低的方式
简单方式:
时间复杂度:O(n)
,空间复杂度O(n)
代码:
class Solution {
public:
string replaceSpace(string s) {
string res;
for(auto c:s)
{
if(c==' ')
{
res+="%20";
}
else
{
res.push_back(c);
}
}
return res;
}
};
性能更优方式:
时间复杂度:O(n)
,空间复杂度O(1)
代码:
// 原地做法:
class Solution
{
public:
string replaceSpace(string s)
{
// 原始大小
int oldSize = s.size();
int count = 0;
for (auto a : s)
{
if (a == ' ')
++count;
}
int newSize = oldSize + 2 * count;
s.resize(newSize); // 扩容字符串
for (int j = oldSize - 1, i = newSize - 1; j < i; --j, --i) // 从字符串末尾开始更新
{
if (s[j] == ' ')
{
s[i] = '0';
s[--i] = '2';
s[--i] = '%';
}
else
{
s[i] = s[j];
}
}
return s;
}
};
总结:
- 本来以为很简单的题目,看了下carl的文章,还有意外的收获
- 做题体验:
嘿嘿
(哈哈
>嘿嘿
>哎哎
>嘤嘤
) - 时间:
未计
二刷
151. 反转字符串中的单词
题目链接:151. 反转字符串中的单词
思路:
- 这题有许多注意的点,一个是反转单词的方式,另一个是去多余空格
- 反转单词:如果想在原地直接移动几个单词的位置比较困难,可以考虑先反转整个字符串,再反转每一个单词
- 去除多余空格,对于前中后的空格都要去除,同时要注意时间复杂度,可以考虑类似
27.移除元素
的思路,用双指针进行覆盖操作
时间复杂度:O(n)
,空间复杂度O(1)
代码:
class Solution
{
public:
string reverseWords(string s)
{
// 先对数组进行逆序操作
std::reverse(s.begin(), s.end());
// 按要求去除多余空格
int left = 0;
int right = 0;
// 先去掉前面空格
while (right < s.size() && s[right] == ' ') // 注意检查越界
++right;
// 再去掉中间空格
for (; right < s.size(); ++right)
{
// 思路和代码随想录相反
if (s[right] != ' ' || (right > 0 && s[right] == ' ' && s[right - 1] != ' '))
{
s[left] = s[right];
++left;
}
}
// 去掉最后空格
if (left > 0 && s[left - 1] == ' ') // 注意检查越界
--left;
s.resize(left);
// 反转每一个单词
auto begin = s.begin();
auto end = s.begin();
for (auto it = s.begin();; ++it)
{
if (*it == ' ')
{
end = it;
std::reverse(begin, end);
begin = ++end;
}
else if (it == s.end())
{
end = it;
std::reverse(begin, end);
break;
}
}
return s;
}
};
总结:
- 这题就败在了去除多余空格上,而去除空格使用的
27.移除元素
的方式,当时没有自己看carl哥的方法,执迷用自己的思路 - 以后就算思路不同,也得多看看carl哥提供的,哈哈
- 做题体验:
哎哎
(哈哈
>嘿嘿
>哎哎
>嘤嘤
) - 时间:
未计
一刷
剑指 Offer 58 - II. 左旋转字符串
思路:
- 这边我写三个解法,第一个最好,时间复杂度是
O(1)
,后面两个是O(n)
,单纯记录下 - 解1:翻转前n个字符,翻转剩余字符,翻转全部字符
- 解2:用模的思想用一个循环,循环内一行代码操作
- 解3:简单地累加两段字符串
解1
时间复杂度:O(n)
,空间复杂度O(1)
代码:
class Solution
{
public:
string reverseLeftWords(string s, int n)
{
std::reverse(s.begin(),std::next(s.begin(),n));
std::reverse(std::next(s.begin(),n),s.end());
std::reverse(s.begin(),s.end());
return s;
}
};
解2
时间复杂度:O(n)
,空间复杂度O(n)
代码:
class Solution
{
public:
string reverseLeftWords(string s, int n)
{
string str;
for (int i = n; (i % s.size() != n) || i == n; ++i)
str += s[i % s.size()];
return str;
}
};
解3
时间复杂度:O(n)
,空间复杂度O(n)
代码:
class Solution
{
public:
string reverseLeftWords(string s, int n)
{
std::string str(std::next(s.begin(), n), s.end());
str += std::string(s.begin(), std::next(s.begin(), n));
return str;
}
};
总结:
- 这里用到了一个std库中的方法
std::next()
,代码中的std::next(s.begin(),n)
和s.begin()+n
有啥区别?来看看小江给我的回答:
xxx.begin()会返回一个迭代器
但迭代器类型有6种
让一个迭代器+n必须满足random_access_iterator_tag
但std::next会对所有迭代器进行特化(特殊处理)
比如vector就是连续型迭代器所以+n等同于std::next(xxx,n)
因为vector底层数据就是连续排布的
但是如果是list双向迭代器,它底层是不连续的
list.begin()+n就是违法操作
但std::next(list.begin(),n)就是合法的
它会让list.begin()连续++ n次
- 做题体验:
嘿嘿
(哈哈
>嘿嘿
>哎哎
>嘤嘤
) - 时间:
未计
一刷