题目1:344 反转字符串
题目链接:反转字符串
对题目的理解
将给定的字符串反转 空间是O(1)
自己的思考
遍历字符串然后存放到新的数组里,但是不满足空间O(1)
想到反转链表使用双指针的思想,所以使用双指针法反转字符串,字符串也是一种数组,所以元素在内存中是连续分布,定义两个指针i,j,一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。
- 时间复杂度: O(n)
- 空间复杂度: O(1)
使用swap交换两个指针处的值。
class Solution {
public:
void reverseString(vector<char>& s) {
for(int i=0, j=s.size()-1;i<s.size()/2;i++,j--){
swap(s[i],s[j]);
}
//这个是void类型,没有返回值
}
};
swap有两种实现方式
1.数值交换
int tmp = s[i];
s[i] = s[j];
s[j] = tmp;
2.位运算
s[i] ^= s[j];
s[j] ^= s[i];
s[i] ^= s[j];
题目2:541 反转字符串Ⅱ
题目链接:反转字符串Ⅱ
对题目的理解
i)根据整数k,从字符串开头计数,每次计数到2k,就反转2k中的前k个字符;
ii)剩余字符少于k个,则剩余字符全部反转;
iii)剩余字符大于等于k,小于2k,则只反转前k个字符
自己的思考
首先应用字符串的长度对2k取余, 先处理剩下的字符串,使用for循环+swap函数反转;然后用字符串的长度除以2k,看有多少个2k,然后再对每一个字符串内的数据进行for循环+swap函数,很冗余,不推荐。
使用reverse库函数(左闭右开)
在遍历字符串的过程中,只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。
因为要找的也就是每2 * k 区间的起点,这样写,程序会高效很多。
所以当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。
- 时间复杂度: O(n)
- 空间复杂度: O(1)
class Solution {
public:
string reverseStr(string s, int k) {
for(int i=0;i<s.size();i+=(2*k)){
if(i+k<=s.size())
reverse(s.begin()+i,s.begin()+i+k);//s[i+k]有值不为空
else reverse(s.begin()+i, s.end());
}
return s;
}
};
之所以是i+k<=s.size(),为什么有等号,可以举一个典型的例子:
abc字符串 k=3 当i=0时,i+k=3,而s.size()=3,因此可以取等号
自己编写反转程序reverse函数的代码(左闭右闭)
- 时间复杂度: O(n)
- 空间复杂度: O(1)或O(n), 取决于使用的语言中字符串是否可以修改.
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]);
}
string reverseStr(string s, int k) {
for(int i=0;i<s.size();i+=(2*k)){
if(i+k<=s.size()){
reverse(s, i, i+k-1);//s[i+k]有值不为空
continue;}
reverse(s, i, s.size()-1);
}
return s;
}
};
另一种思路解法
- 时间复杂度: O(n)
- 空间复杂度: O(1)
class Solution {
public:
string reverseStr(string s, int k) {
int n = s.size(),pos = 0;
while(pos<n){
if(pos+k<n){
reverse(s.begin()+pos, s.begin()+pos+k);
}
else reverse(s.begin()+pos,s.end());
pos += 2*k;
}
return s;
}
};
但是这个程序里面有一个疑问:???就是这段代码为什么会报错???
注意分情况要画{}呀,是因为没有打印{},所以continue会直接拦截住 reverse(s.begin()+i, s.end()),使这一句永远都不会执行。
class Solution {
public:
string reverseStr(string s, int k) {
for(int i=0;i<s.size();i+=(2*k)){
if(i+k<=s.size()){
reverse(s.begin()+i,s.begin()+i+k);//s[i+k]有值不为空
continue;}
reverse(s.begin()+i, s.end());
}
return s;
}
};这段代码为什么会计算出错
题目3:05替换空格
题目链接:替换空格(链接是力扣的)
对题目的理解
(力扣:将'.'替换为空格) 将空格换成%20 注意%20其实是3个字符:% 2 0
自己的思考
还是遍历的思想,遇到空格,就替换为20%,不行,一个位置只能换一个位置,但是20%有两个位置,无法换
双指针法
首先扩充数组到每个空格替换成"%20"之后的大小。先数有多少个空格,有多少个就在后面补上它的2倍,因为有3个字符嘛,相比原来,多了两个个字符;
然后从后向前替换空格,也就是双指针法,过程如下:
i指向新长度的末尾,j指向旧长度的末尾。
当j指针指向不为空格的元素时,将该元素后移到i指针指向的位置,然后i指针和j指针同时向前移一位再判断j指针指向的位置处是否为空格,若不是空格,则继续讲该元素移到i指针指向的位置,若是j指针指向的位置是空格,则i指针处填冲字符0,然后i指针向前移动一位,填充字符2,i指针再向前移动一位,填充%,然后j指针再移动,继续判断该位置处的字符是否为空格.
很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这样做有许多好处:
- 不用申请新数组。
- 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
剑指offer题目(20%)
- 时间复杂度:O(n)
- 空间复杂度:O(1)
class Solution {
public:
string replaceSpace(string s) {
int count = 0; // 统计空格的个数
int sOldSize = s.size();
for (int i = 0; i < s.size(); i++) {
if (s[i] == ' ') {
count++;
}
}
// 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小
s.resize(s.size() + count * 2);
int sNewSize = s.size();
// 从后先前将空格替换为"%20"
for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
if (s[j] != ' ') {
s[i] = s[j];
} else {
s[i] = '0';
s[i - 1] = '2';
s[i - 2] = '%';
i -= 2;
}
}
return s;
}
};
力扣题目(.换成空格)
//力扣题目代码
class Solution {
public:
string pathEncryption(string path) {
int count = 0;//统计.的个数
int size = path.size();
for(int i=0;i<path.size();i++){
if(path[i]=='.'){
path[i] = ' ';//计数.的个数
}
}
return path;
}
};
题目4:151翻转字符串里的单词
题目链接:翻转字符串里的单词
对题目的理解
反转字符串中单词(没有空格)的顺序,s中使用大于等于1个空格将单词分开,返回的单词顺序颠倒且仅在单词之间用单个空格分隔。
自己的思考
以空格结束作为一个单词的标志,或者是判断一个单词,判断这个单词两端都要空格,采用双指针
双指针法
本题不使用辅助空间,空间复杂度为O(1)
解题思路
i)移除多余空格
ii)将整个字符串反转
iii)将每个单词反转
所以,将这段程序进行拆解,分解成两个任务,去空格和翻转
翻转的程序好写
void reverse(string& s,int start,int end){
for(int i=start,j=end;i<j;i++,j--){
swap(s[i],s[j]);
}//定义的是一个左闭右闭区间,因为第三个参数是end
}
难点是去空格的程序,因为是有选择的去空格,该段程序有两个解法
①分类讨论去空格
void removeExtraSpaces(string& s){
int slow = 0, fast = 0;
//去掉字符串前面的空格
while(s.size()>0 && fast<s.size() && s[fast] == ' ' ){
fast++;
}//此时fast最后指向第一个不为空格的元素,即第一个单词的第一个英文字母
for(; fast<s.size();fast++){
//去掉字符串中间部分的冗余空格
if(fast-1>0 && s[fast-1]==s[fast] && s[fast]==' '){
continue;
}//知道fast的紧挨着的两个元素不是空格时为止
else{
s[slow] = s[fast];
slow++;
}//slow指向不为空格的元素,即英文字母
}
if(slow-1>0 && s[slow-1]==' '){//去掉字符串末尾的空格
s.resize(slow-1); //resize操作
}
else{
s.resize(slow);//重新设置字符串的大小
}
}
②使用双指针法去除空格(这个对我来讲有点难理解)
void removeExtraSpaces(string& s){//去除所有空格,并在相邻单词之间添加空格,快慢指针
int slow = 0;
for(int fast=0;fast<s.size();fast++){
if(s[fast]!=' '){
if(slow!=0) s[slow++] = ' ';
while(fast<s.size() && s[fast]!=' '){
s[slow++] = s[fast++];
}
}
}
s.resize(slow);
}
完整代码(解法1):
- 时间复杂度: O(n)
- 空间复杂度: O(1) 或 O(n),取决于语言中字符串是否可变
class Solution {
public:
void removeExtraSpaces(string& s){
int slow = 0, fast = 0;
//去掉字符串前面的空格
while(s.size()>0 && fast<s.size() && s[fast] == ' ' ){
fast++;
}//此时fast最后指向第一个不为空格的元素,即第一个单词的第一个英文字母
for(; fast<s.size();fast++){
//去掉字符串中间部分的冗余空格
if(fast-1>0 && s[fast-1]==s[fast] && s[fast]==' '){
continue;
}//知道fast的紧挨着的两个元素不是空格时为止
else{
s[slow] = s[fast];
slow++;
}//slow指向不为空格的元素,即英文字母
}
if(slow-1>0 && s[slow-1]==' '){//去掉字符串末尾的空格
s.resize(slow-1); //resize操作
}
else{
s.resize(slow);//重新设置字符串的大小
}
}
void reverse(string& s,int start,int end){
for(int i=start,j=end;i<j;i++,j--){
swap(s[i],s[j]);
}//定义的是一个左闭右闭区间,因为第三个参数是end
}
string reverseWords(string s) {
removeExtraSpaces(s);//先去除字符串中没有必要的空格
reverse(s,0,s.size()-1);//将整个字符串颠倒顺序是因为后续进行reverse时,i-1,即对应s.size()-1,这里一定要是小于号,不然会报错
int start = 0; //第一个单词的下标一定是0,
for(int i=0;i<=s.size();i++){//这里是小于等于
if(i==s.size() || s[i]==' '){ //到达空格或串尾,说明一个单词结束,进行反转
reverse(s,start,i-1);//注意这是自己编写的reverse函数,是左闭右闭的反转
start = i+1;//s[i]处是空格,所以start从i+1处开始
}
}
return s;
}
};
完整代码(解法2):
- 时间复杂度: O(n)
- 空间复杂度: O(1) 或 O(n),取决于语言中字符串是否可变
class Solution {
public:
void removeExtraSpaces(string& s){//去除所有空格,并在相邻单词之间添加空格,快慢指针
int slow = 0;
for(int fast=0;fast<s.size();fast++){
if(s[fast]!=' '){
if(slow!=0) s[slow++] = ' ';
while(fast<s.size() && s[fast]!=' '){
s[slow++] = s[fast++];
}
}
}
s.resize(slow);
}
void reverse(string& s,int start,int end){
for(int i=start,j=end;i<j;i++,j--){
swap(s[i],s[j]);
}//定义的是一个左闭右闭区间,因为第三个参数是end
}
string reverseWords(string s) {
removeExtraSpaces(s);//先去除字符串中没有必要的空格
reverse(s,0,s.size()-1);//将整个字符串颠倒顺序是因为后续进行reverse时,i-1,即对应s.size()-1,这里一定要是小于号,不然会报错
int start = 0; //第一个单词的下标一定是0,
for(int i=0;i<=s.size();i++){//这里是小于等于
if(i==s.size() || s[i]==' '){ //到达空格或串尾,说明一个单词结束,进行反转
reverse(s,start,i-1);//注意这是自己编写的reverse函数,是左闭右闭的反转
start = i+1;//s[i]处是空格,所以start从i+1处开始
}
}
return s;
}
};
将代码自己按照逻辑又过了一遍,添加了一些没有的注释。
class Solution {
public:
//首先去除字符串中所有的空格,定义一个函数
void removespace(string& s){
//空格在开头
int slow = 0, fast = 0;
while(s.size()>0 && fast<s.size() && s[fast]==' '){
fast++;//保证开头的空格都删掉,fast此时指向第一个单词的第一个字母
}
//空格在中间,若单词之间的空格多于一个,则应该删除这个空格
for(;fast<s.size();fast++){
if(fast-1>0 && s[fast]==s[fast-1] && s[fast]==' '){
continue;
}
else s[slow++] = s[fast];
}
//空格在结尾
if(slow-1>0 && s[slow-1]==' '){
s.resize(slow-1);//这里为啥要是slow-1呢,因为前一段处理中间的数据时,for循环最后进行了slow++
}
else s.resize(slow);
}
//接着翻转,定义一个函数
void reverse(string& s,int start,int end){
for(int i=start,j=end;i<j;i++,j--){
swap(s[i], s[j]);//这是一个左闭右闭区间
}
}
string reverseWords(string s) {
//首先去除空格
removespace(s);
//翻转
reverse(s,0,s.size()-1);
//将每个单词进行翻转
//每个单词的确定条件是遇到了空格
int start = 0;
for(int i=0;i<=s.size();i++){
if(s[i]== ' ' || i==s.size()){
reverse(s,start,i-1);//这里start,i-1传递的是位置
start = i+1;//start代表一个单词的起始位置,一定要在if判断里面,不能在if判断外面
}
}
return s;
}
};
题目5:58-Ⅱ 左旋转字符串
题目链接:左旋转字符串
对题目的理解
把字符串前面的若干个字符转移到字符串的尾部。
解法
局部反转+整体反转
具体步骤为:
- 反转区间为前n的子串
- 反转区间为n到末尾的子串
- 反转整个字符串
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;
}
};