剑指offer05.替换空格
请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
思路:
在cpp中不能直接将字符串赋值给字符,也就是说,不能通过 s[i]="%20"
这样的方法来进行替换。s[i] 是一个字符,将字符串 “%20” 直接赋值给它是不可能的。
同时,为了降低空间复杂度,我们最好在原字符串上进行操作
错误示例:
string replaceSpace(string s) {
for(int i=0;i<s.size();i++){
if(s[i]==' '){
s[i]="%20"; //错误,字符串不能直接赋值给字符
}
}
return s;
方法一:可以调用replace
(但是这道题目调用replace直接解决,没有意义)
class Solution {
public:
string replaceSpace(string s) {
size_t p = 0; //size_t存储字符串中空格的位置
//注意size_t就是s.find()变量的返回值
while ((p = s.find(" ", p)) != std::string::npos) {
s.replace(p, 1, "%20");
}
return s;
}
};
replace方法的时间复杂度与空间复杂度
replace的时间复杂度是O(n),因为它需要先找到要替换的位置,这个过程就是**O(n)**的。而且,如果替换后的字符串长度和原来不一样,那么还需要移动后面的字符,这个过程也是O(n)的。所以,replace的时间复杂度是O(n)。
示例:
void replaceAll(std::string& str, const std::string& from, const std::string& to) {
if(from.empty())
return;
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
}
}
replace方法的空间复杂度取决于你是用什么容器来存储字符串的。如果你用的是**std::string
,那么replace方法的空间复杂度是O(1),因为它不会分配新的内存**,而是直接修改原字符串。但是,如果你用的是其他容器,比如**std::vector
,那么replace方法的空间复杂度可能是O(n)**,因为它可能需要重新分配内存来适应新的字符串长度。
示例:
// replace algorithm example
#include <iostream> // std::cout
#include <algorithm> // std::replace
#include <vector> // std::vector
int main () {
int myints[] = { 10, 20, 30, 30, 20, 10, 10, 20 };
std::vector<int> myvector (myints, myints+8); // 10 20 30 30 20 10 10 20
std::replace (myvector.begin(), myvector.end(), 20, 99); // 10 99 30 30 99 10 ...
}
字符串s.find()和map中find()方法的区别?
1.字符串的s.find()方法
std::string
的 find
函数和 std::map
的 find
函数有不同的用法和目的。
std::string::find
函数是用于在字符串中查找子串的位置。它的语法如下:
size_t find(const std::string& str, size_t pos = 0) const;
str
是要查找的子串,可以是一个字符串或字符。pos
是可选参数,指定从哪个位置开始搜索子串。默认值是0,表示从字符串的起始位置开始搜索。- 它返回找到的子串的第一个字符在字符串中的位置索引,如果未找到则返回
std::string::npos
。
示例:
std::string s = "Hello, World!";
size_t pos = s.find("World"); // pos = 7,找到了子串"World"
2.map
的.find()方法
std::map::find
函数是用于在 std::map
容器中查找给定键的迭代器。它的语法如下:
iterator find(const Key& key);
key
是要查找的键。- 它返回指向包含给定键的元素的迭代器,如果未找到则返回
end()
迭代器。
示例:
std::map<int, std::string> myMap;
myMap[1] = "One";
myMap[2] = "Two";
auto it = myMap.find(2); // it 指向键为 2 的元素
总结来说,std::string::find
用于在字符串中查找子串的位置,而 std::map::find
用于在 std::map
容器中查找给定键的迭代器。它们的用法和返回值不同,但都提供了在特定容器中进行查找的功能。
size_t的用法
size_t
和 map中的auto
在这里都用于存储返回的结果,但它们的用途和含义有所不同。
size_t
是一种无符号整数类型,通常用于表示数组的大小、索引或字符串的长度。在字符串的find
函数中,size_t
用于表示找到的子串的位置索引或无效位置(std::string::npos
)。auto
是一个类型推导关键字,它可以根据右侧表达式的类型自动推导出变量的类型。在std::map
的find
函数中,auto
用于自动推导返回的迭代器的类型。
虽然在这两个情况下都可以使用 size_t
和 auto
存储返回的迭代器或位置索引,但它们的用法和意义是不同的。size_t
是一个特定的整数类型,而 auto
是一种类型推导机制。
size_type用法
std::string::find
函数的返回类型是 std::string::size_type
,而不是 size_t
。
std::string::size_type
是一个无符号整数类型,用于表示字符串的大小和索引。它是根据特定平台上字符串的最大长度进行定义的。在大多数情况下,std::string::size_type
的别名是 size_t
,因此两者在大多数情况下是等价的。
方法二:扩充空间(常规解法)
1.先把数组扩充到填充了%20字符之后的大小
2.再按照双指针法从后向前替换空格
从后向前替换的原因
如果扩充空间之后,再从前向后填充,就是**O(n^2)**的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。
其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这样做有两个好处:
- 不用申请新数组。空间复杂度为O(1),不分配新的内存
- 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
class Solution {
public:
string replaceSpace(string s) {
//先计算有多少个空格
int count=0;
int oldSize = s.size(); //记住旧的数组长度
for(int i=0;i<s.size();i++){
if(s[i]==' '){
count++;
}
}
s.resize(s.size()+count*2);
int newSize = s.size(); //新的数组长度
//从后往前遍历,用新的数组部分存放旧的部分
for(int i=newSize-1,j=oldSize-1;j<i;i--,j--){ //i可以重复赋值
if(s[j]!=' '){
s[i]=s[j];
}
else{
s[i]='0';
s[i-1]='2';
s[i-2]='%';
i=i-2;//注意这里的i只会比j多出两个格子,不是3个!
}
}
return s;
}
};
注意
本题目中i只比j多出两个空格,因此i往后移动的时候应该是i-=2。
边界条件直接猜测空格在最后一个即可
截止目前,加上本题已经做了8道双指针相关的题目,分别是:
- 27.移除元素(opens new window)
- 15.三数之和(opens new window)
- 18.四数之和(opens new window)
- 206.翻转链表(opens new window)
- 142.环形链表II(opens new window)(环形链表题目有待补充)
- 344.反转字符串(opens new window)
- [151.反转字符串里的单词(opens new window)](代码随想录 (programmercarl.com))
补充1:url
当在URL中传递参数时,某些字符(例如空格、特殊字符等)需要进行编码,以便在URL中进行传输和解析时不会造成歧义或错误。URL编码是将这些字符转换为特定格式的编码表示。
URL编码使用百分号(%)后跟两个十六进制数字来表示特定字符的编码。**对于空格字符,其编码为%20
。**这意味着在URL中,空格字符会被替换为%20
,以确保在接收方解码时能够正确还原为空格字符。
例如,考虑以下URL:https://example.com/search?query=my search string
。在这个URL中,空格字符存在于查询参数中的字符串my search string
中。为了将这个URL传递给服务器或进行处理,空格字符需要进行编码。因此,这个URL在进行URL编码后的结果可能如下所示:https://example.com/search?query=my%20search%20string
。在编码后的URL中,空格字符被替换为%20
。
URL编码是确保在URL中传递特殊字符和保留字符时的一种常见做法,以避免URL解析和传输中的问题。编码和解码过程是互逆的,可以通过URL解码将编码后的字符重新还原为其原始形式。
补充2:字符串和数组的区别
字符串是若干字符组成的有限序列,也可以理解为是一个字符数组,但是很多语言对字符串做了特殊的规定,接下来我来说一说C/C++中的字符串。
在C语言中,把一个字符串存入一个数组时,也把结束符 '\0’存入数组,并以此作为该字符串是否结束的标志。
示例:
char a[5] = "asd";
for (int i = 0; a[i] != '\0'; i++) {
}
这里如果是cpp,同样含义的代码应该用string类类型来写,比如:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string a = "asd"; // 定义一个string对象a,初始化为"asd"
for (int i = 0; i < a.size(); i++) // 用一个for循环遍历a,用a.size()获取字符串的长度
{
// 循环体里可以对a[i]做任何操作,比如打印
cout << a[i] << endl;
}
return 0;
}
在C++中,提供一个string类,string类会提供 size接口,可以用来判断string类字符串是否结束,就不用’\0’来判断是否结束。
示例:
string a = "asd";
for (int i = 0; i < a.size(); i++) {
}
那么vector< char >
和 string
又有什么区别呢?
其实在基本操作上没有区别,但是 string提供更多的字符串处理的相关接口,例如string 重载了+,而vector却没有。
所以想处理字符串,我们还是会定义一个string类型。
补充3:char
数组创建字符串的注意事项
要保证char数组用’\0’结尾才能转换为 string,要么直接就用string 才创建结果字符串
以上两种方式都可以,注意char数组如果要存放字符串,最后一位需要变成’\0’
当你有一个char数组存储字符串时,确实需要在最后一个字符后面加上’\0’字符。例如:
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
或者你可以直接初始化字符串,编译器会自动在末尾添加’\0’字符:
char str[] = "Hello";
在这个例子中,str
实际上是一个包含6个字符的数组,字符是:‘H’, ‘e’, ‘l’, ‘l’, ‘o’, 和 ‘\0’。
如果你忘记在字符串末尾加’\0’,那么任何试图读取这个字符串的函数(如printf,strcpy
等)可能会继续读取内存,直到它找到一个’\0’字符。这可能导致读取超出数组边界的内存,这是一个常见的导致程序错误的原因,称为缓冲区溢出。
在C++中,也可以使用 std::string
类来存储和操作字符串,它会自动处理这种终止字符的问题,使得字符串的使用更加方便和安全。