【面试题05】替换空格
难度: 简单
限制: 0 <= s 的长度 <= 10000
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
Leetcode题目对应位置: 面试题05:替换空格
(python 写的话没有还原到原题目考点的精髓,建议使用 C++)
在 Python 语言中,字符串是不可变类型,即无法修改字符串中的某一位字符,所以用 Python 语言写的话,需要新建一个字符串来实现题目要求。
啰嗦一下,python 字符串不可变的意思是你不能用 a[0]='p'
这种方式对字符串进行改变,而 a = 'dadsa'
这种方式实际是重新赋值,重新赋值是允许的。
代码逻辑:
1)创建一个新列表,t;
2)循环遍历字符串 s 中的每个字符 c,若 c 是空格,则向列表 t 中添加字符串 “%20”;否则向列表 t 中添加字符 c;
3)将列表 t 转化为字符串并返回。
时间复杂度:O(n),需要遍历整个长度为 n 的字符串 s。
空间复杂度:O(n),需要额外的线性辅助空间。
# python
class Solution:
def replaceSpace(self, s: str) -> str:
length = len(s)
new = [0] * (len(s) * 3)
size = 0
for ss in s:
if ss is ' ':
new[size] = '%'
new[size+1] = '2'
new[size+2] = '0'
size += 3
else:
new[size] = ss
size += 1
return ''.join(new[:size])
# 参考1
class Solution:
def replaceSpace(self, s: str) -> str:
res = []
for c in s:
if c == ' ': res.append("%20")
else: res.append(c)
return "".join(res)
# 参考2
class Solution:
def replaceSpace(self, s: str) -> str:
return ''.join(('%20' if c==' ' else c for c in s))
在这篇解答里也提到了另一种连接字符串的方法,就是用 +
:
# 参考3
class Solution:
def replaceSpace(self, s: str) -> str:
res = ""
for c in s:
if c == ' ': res += "%20"
else: res += c
return res
但是当连接次数过多时,使用 +
的方法性能会急剧下降,参考文章:你所不知道的Python | 字符串连接的秘密,所以比较推荐的是 .join
方法。
在剑指 offfer 原书中,这道题实际上是想考察 对内存覆盖的理解 和 分析时间效率 的能力。假设保证输入的字符串后面有足够多的空余内存,要求在原本的字符串上进行替换。这里有两种思路:第 1 个是从前往后遍历替换,第 2 个是从后往前遍历替换(第二种才是比较推荐的巧妙的解法)。
官方代码: 05_ReplaceSpaces/ReplaceSpaces.cpp(第二种方法)
思路一: 从前往后遍历替换
从前往后遍历字符串,每遇到一个空格就将其后的所有字符后移两位。
时间复杂度:O(n^2),如果字符串长度为 n,对于每个空格字符都需要移动其后面的 O(n) 个字符
空间复杂度:O(1)
需要注意的细节: 当遇到空格字符后,会由原来的 1 个空格字符替换为 3 个字符,所以遍历字符串的指针 i i i 需要对应 +2,才能继续指向原字符串的下一个字符,否则会指向字符 “2”。
#include
#include
void ReplaceBlank(char str[], int length)
{
if (str == nullptr && length <= 0)
return;
/*计算字符串实际长度*/
int oriLength = 0;
while (str[oriLength] != '\0')
{
oriLength++;
}
/*从左向右替换字符串中的空格*/
int count = 0; // 用于记录当前是第几个空格
for (int i = 0; i < oriLength; i++)
{
int idx = i + 2 * count;
if (str[idx] == ' ')
{
/*将空格后的字符依次后移两位*/
for (int j = oriLength + 2 * count; j > idx; j--)
{
str[j + 2] = str[j];
}
str[idx] = '%';
str[idx + 1] = '2';
str[idx + 2] = '0';
count++;
}
}
}
思路二: 从后往前遍历替换
自己写的代码逻辑:
1)先遍历一遍字符串,统计出字符串的实际长度和空格字符的个数;
2)从后向前遍历字符串,每遇到一个字符,计算该字符应该移动到的位置,也就是 new index,new index = 字符串实际长度 + 尚未遇到的空格字符个数 * 2
,需要注意的是,如果遇到的是空格字符,则尚未遇到的空格字符数需要减 1;
3)设置一个额外变量 count,用于记录当前还剩下几个空格没有遇到,且每遇到一个空格 count - 1。
#include
#include
void ReplaceBlank(char str[], int length)
{
if (str == nullptr && length <= 0)
return;
/*统计字符串的实际长度oriLength
及空格个数spacNum*/
int i = 0;
int oriLength = 0;
int spacNum = 0;
while (str[i] != '\0')
{
oriLength++;
if (str[i] == ' ')
{
spacNum++;
}
i++;
}
/*判断是否会越界*/
int newLength = oriLength + spacNum * 2;
if (newLength > length)
return;
/*从后往前遍历字符串*/
int count = spacNum;
int newId = 0;
for (i = oriLength; i >= 0; i--)
{
if (str[i] == ' ')
{
newId = i + (count - 1) * 2; // 注意这里尚未遇到的空格字符要减1
str[newId] = '%';
str[newId+1] = '2';
str[newId+2] = '0';
count--;
}
else
{
newId = i + count * 2;
str[newId] = str[i];
}
}
}
官方给出的代码逻辑 ⭐:
1)定义两个指针p1、p2,初始时 p1 指向原字符串的末尾,p2 指向置换后字符串的末尾;
2)向前移动指针 p1,将它指向的字符复制到 p2 所指向的位置,直到碰到第一个空格字符为止;
3)碰到第一个空格字符后,将 p1 向前移动 1 步,在 p2 位置上放字符 “0”,p2 继续向前移动了 2 步,这两步上分别放 “2” 和 “%”,然后再将 p2 向前移动 1 步,这时 p1 又指向了一个不为空格字符的字符,p2 仍是其对应的新插入位置;
4)当 p1 和 p2 指向同一位置时,说明所有空格都已经替换完毕,直接退出。
这个方法灵性就灵性在于,p1 每次只走 1 步,而 p2 在 p1 指向非空格字符时走 1 步,在 p1 指向空格字符时走 3 步,所以假设有 m 个空格字符,那么 p2 与 p1 之间就相差 2m 步,每遇到一个空格,p2 与 p1 之间的差距就减少 2 步,所以在遇到第 m 个空格后,p2 与 p1 之间的差距恰好为 0,也就是 p2 恰好追上了 p1。比自己写的代码优就优在省去了当前面不再有空格字符时的判断,学习了!
#include
#include
/*length 为字符数组str的总容量,大于或等于字符串str的实际长度*/
void ReplaceBlank(char str[], int length)
{
if(str == nullptr && length <= 0)
return;
/*originalLength 为字符串str的实际长度*/
int originalLength = 0;
int numberOfBlank = 0;
int i = 0;
while(str[i] != '\0')
{
++ originalLength;
if(str[i] == ' ')
++ numberOfBlank;
++ i;
}
/*newLength 为把空格替换成'%20'之后的长度*/
int newLength = originalLength + numberOfBlank * 2;
if(newLength > length)
return;
int indexOfOriginal = originalLength;
int indexOfNew = newLength;
while(indexOfOriginal >= 0 && indexOfNew > indexOfOriginal)
{
if(str[indexOfOriginal] == ' ')
{
str[indexOfNew --] = '0';
str[indexOfNew --] = '2';
str[indexOfNew --] = '%';
}
else
{
str[indexOfNew --] = str[indexOfOriginal];
}
-- indexOfOriginal;
}
}
在 LeetCode 运行:
//C++
class Solution {
public:
string replaceSpace(string s) {
if(s.length()==0)
{
return s;
}
int oriLength = s.size() - 1;
int spacNum = 0;
/*统计空格字符个数*/
for(auto c:s)
{
if(c == ' ')
{
++spacNum;
}
}
int newLength = oriLength + spacNum * 2;
s += string(spacNum * 2,' '); // 这里为了保证有足够的空间容纳新字符串
/*从后往前遍历字符串*/
while(oriLength >= 0 && newLength > oriLength)
{
if(s[oriLength] == ' '){
s[newLength--] = '0';
s[newLength--] = '2';
s[newLength--] = '%';
}
else{
s[newLength--] = s[oriLength];
}
oriLength--;
}
return s;
}
};
参考资料:
[1] LeetCode 面试题05:替换空格
[2] 参考题解 - 作者:Krahets
[3] 你所不知道的Python | 字符串连接的秘密
[4] 剑指 offer 第二版