当前位置: 首页 > news >正文

剑指offer第2版——面试题5:替换空格

一、题目

请实现一个函数,把字符串s中的每个空格替换成 “%20”。
示例:
输入:s = "We are happy."
输出:"We%20are%20happy."
限制:0 <= s的长度 <= 10000

二、答案

2.1 我的答案

暴力求解法,时间复杂度较高!

#include <string>
#include <iostream>
using namespace std;
/*把空格替换成%20*/
void repalceBlock(char* aim, int len)
{if (nullptr == aim|| len<=0){return;// 立即结束函数,不再执行后面的代码}/*计算出有多少个空格*/int numberOfBlock = 0;int originLen = 0;int i = 0;while (aim[i] != '\0'){originLen++;if (' ' == aim[i]){numberOfBlock++;}i++;}cout << "空格数量是:" << numberOfBlock <<endl;int newLength = originLen + numberOfBlock * 2;if (newLength > len) {return; // 缓冲区不足,无法完成替换}int p1 = originLen;int p2 = newLength;while (p1>=0 && p2>p1){if (aim[p1] == ' '){aim[p2--] = '0';aim[p2--] = '2';aim[p2--] = '%';}else{aim[p2--] = aim[p1];}p1--;}}int main() {char aim[20] = "We are happy";repalceBlock(aim,sizeof(aim));cout << aim;return 0;
}

2.2 标准答案

#include <string>
#include <iostream>
using namespace std;
/*把空格替换成%20*/
string replaceSpaces(string& str) {int len = str.length();int space = 0;// 统计空格数量for (char c : str) {if (c == ' ') {space++;}}// 修改字符串长度str.resize(len + 2 * space);int p1 = len - 1;int p2 = str.length() - 1;while (p1 >= 0) {if (str[p1] == ' ') {str[p2--] = '0';str[p2--] = '2';str[p2--] = '%';}else {str[p2--] = str[p1];}p1--;}return str;
}int main() {string aim = "We are happy";replaceSpaces(aim);cout << aim;return 0;
}

2.3 解析

参考剑指向offer

三、考察点

这个问题看似简单,实则考察以下编程基础:

  1. 字符串操作能力:如何高效遍历字符串、处理字符替换(尤其需要考虑原字符串长度变化对内存的影响)。
  2. 边界情况处理:空字符串、连续空格(如"a b")、字符串首尾空格等场景的正确处理。
  3. 时间与空间效率意识:例如在 C++ 中,若先计算空格数量再扩容字符串,可以避免频繁的内存重新分配,提升效率。

总之,个问题的本质是对网络通信中字符编码规则的实际应用,同时考察基础的字符串处理逻辑。

四、背景知识

在字符串中替换空格为%20的需求,本质上与URL 编码规则密切相关,是网络通信和数据传输中非常基础且重要的处理逻辑。以下从具体场景和技术原理两方面详细讲解:

核心需求来源:URL 的特殊字符编码规则

在网络传输中,URL(统一资源定位符)是标识资源位置的字符串,例如https://www.example.com/search?query=hello world。但 URL 中不允许直接包含空格等特殊字符,原因是:

  • 早期的网络协议(如 HTTP)规定,URL 只能由字母、数字和部分符号(如-_.等) 组成,空格不在允许范围内。
  • 若直接包含空格,会导致 URL 解析错误(例如服务器无法区分空格是参数的一部分,还是分隔符)。

因此,需要一种编码方式将空格等特殊字符转换为 URL 允许的格式,%20就是空格的URL 编码结果(其中20是空格字符在 ASCII 表中的十六进制值,%是编码标识)。

实际应用场景:

  1. 网页表单提交
    当用户在搜索框、输入框中输入包含空格的内容(如 “hello world”),浏览器会自动将空格替换为%20后再发送请求。例如:
    输入内容"We are happy",提交后实际发送的 URL 参数会变为We%20are%20happy
  2. URL 参数拼接
    在后端开发中,若需要动态生成包含空格的 URL(如根据用户输入拼接参数),必须手动替换空格为%20,否则会导致链接失效。例如:
    拼接用户昵称"John Doe"作为参数时,需转换为"John%20Doe"才能正确被服务器解析。
  3. 文件路径处理
    部分系统或协议中,文件路径中的空格也需要编码为%20(尤其是跨平台传输时),避免因操作系统对空格的处理差异导致错误。1

五、扩展知识

5.1 知识1:字符数组如何作为函数参数传递?

char aim[] = "We are happy";

5.1.1 传递数组名(隐式转换为指针)

字符数组名在作为参数时会隐式转换为指向首元素的指针(char*const char*),这是最常用的方式。

(1)传递可修改的字符数组(char* 参数)

如果需要在函数中修改数组内容,参数用 char*

#include <iostream>// 传递char*,可修改数组内容
void modifyArray(char* arr) {if (arr != nullptr) {// 例如:将第一个字符改为大写if (arr[0] >= 'a' && arr[0] <= 'z') {arr[0] -= 32;}}
}int main() {char str[] = "hello";  // 字符数组modifyArray(str);      // 传递数组名(隐式转为char*)std::cout << str << std::endl;  // 输出 "Hello"return 0;
}
(2)传递只读的字符数组(const char* 参数)

如果只需读取数组内容,参数用 const char*(更安全,且兼容字符串字面量):

#include <iostream>
#include <cstring>// 传递const char*,仅读取数组内容
int getArrayLength(const char* arr) {if (arr == nullptr) return 0;return std::strlen(arr);  // 依赖'\0'计算长度
}int main() {char str[] = "world";std::cout << "长度:" << getArrayLength(str) << std::endl;  // 输出 5return 0;
}

5.1.2 传递数组时指定长度(避免依赖 \0

如果字符数组中可能包含 \0(非字符串),或需要精确控制处理范围,需额外传递数组长度:

#include <iostream>// 传递数组指针 + 长度(处理任意字符数组,不依赖'\0')
void printArray(const char* arr, int length) {for (int i = 0; i < length; ++i) {std::cout << arr[i];}std::cout << std::endl;
}int main() {char arr[] = {'h', 'i', '\0', '!', '!'};  // 包含'\0'的字符数组int len = sizeof(arr) / sizeof(arr[0]);   // 计算数组总长度(5)printArray(arr, len);  // 输出 "hi!!"(包含'\0'但仍打印所有元素)return 0;
}

5.1.3 传递数组引用(指定大小,较少用)

可以传递数组的引用,但必须显式指定数组大小(灵活性低,仅适用于固定大小的数组):

#include <iostream>// 传递数组引用(必须指定大小,如[6])
void funcWithArrayRef(char (&arr)[6]) {  // 仅接收大小为6的char数组arr[0] = 'H';
}int main() {char str[] = "hello";  // 大小为6(5个字符 + '\0')funcWithArrayRef(str); // 正确:数组大小匹配std::cout << str << std::endl;  // 输出 "Hello"return 0;
}

关键总结

传递方式语法示例适用场景注意事项
指针(可修改)void func(char* arr)需要修改数组内容,处理字符串(依赖 ‘\0’)需检查空指针,避免越界
指针(只读)void func(const char* arr)仅读取内容,兼容字符串字面量不可修改内容,需检查空指针
指针 + 长度void func(const char* arr, int len)处理含 ‘\0’ 的数组,精确控制范围需手动传递长度,确保不越界
数组引用(固定大小)void func(char (&arr)[N])严格限制数组大小,需精确匹配灵活性低,仅适用于固定大小数组

实际开发中,const char\*(只读)char\*(可修改) 是最常用的方式,配合字符串结束符 \0 处理常规字符串。如果是二进制数据或含 \0 的字符数组,务必额外传递长度参数。

5.2 知识2:“ ”和‘ ’有啥区别?

在 C/C++ 中,' ' == *aim" " == *aim本质区别,前者是正确的,后者会导致编译错误或逻辑错误,具体区别如下:

5.2.1 ' '(单引号):表示单个字符

  • ' '字符常量,代表一个空格字符,类型为 char,在内存中占用 1 字节。
  • *aim 是指针 aim 指向的字符(char 类型),因此 ' ' == *aim正确的字符比较,用于判断当前字符是否为空格。

你的代码中 ' ' == *aim 是正确的写法,用于统计空格数量:

if (' ' == *aim)  // 正确:判断当前字符是否为空格
{numberOfBlock++;
}

5.2.2 " "(双引号):表示字符串字面量

  • " "字符串常量,代表一个包含空格字符和终止符 \0 的字符串(即 {' ', '\0'}),类型为 const char[2](长度为 2 的字符数组)。
  • 当使用 " " 时,它会隐式转换为 const char* 指针(指向字符串首地址)。

此时 "" == *aim 会变成 const char*char 的比较,两者类型完全不匹配:

if (" " == *aim)  // 错误:类型不匹配(const char* vs char)
  • 编译器会直接报错(类型不兼容),因为无法比较一个指针和一个字符。
  • 即使某些编译器允许(实际不会),比较的也是指针地址和字符的 ASCII 码值,逻辑上毫无意义。

5.2.3 总结

  • ' ' 是单个字符,用于与 char 类型的变量(如 *aim)比较,判断是否为空格,你的代码中这部分是正确的。
  • " " 是字符串,无法与单个字符比较,会导致编译错误,绝对不能这样写。

在 C/C++ 中,单引号用于单个字符,双引号用于字符串,这是基础语法规则,必须严格区分。

5.3 指针访问方式:aim[i]等价于*(aim+i)

在 C/C++ 中,指针完全支持 aim[i] 这种访问方式,它与 *(aim + i) 是完全等价(效率完全等价)的,是指针访问元素的两种等效写法。

对于指针 char* aim

  • aim[i] 本质上是 *(aim + i) 的语法糖,编译器会自动将其转换为指针偏移后取值的形式。
  • 例如:
    • aim[0] 等价于 *aim(指针指向的第一个元素)。
    • aim[1] 等价于 *(aim + 1)(指针偏移 1 字节后指向的元素)。
    • 以此类推,aim[i] 就是指针向后偏移 i 个元素后指向的内容。

5.4 为什么 originalLength 不一定等于 len - 1

  • 情况 1:缓冲区未被填满
    若原始字符串实际长度较短(比如字符串是 "a b",实际长度为 3,包含 1 个空格),而缓冲区容量 len 较大(比如 len = 10),则 originalLength = 3,而 len - 1 = 9,显然不相等。
  • 情况 2:缓冲区刚好填满
    若原始字符串刚好占满整个缓冲区(比如字符串长度为 9,len = 10),则 originalLength = 9len - 1 = 9,此时二者相等。
  • 情况 3:字符串超长(不合理但可能存在)
    若原始字符串没有正确的 '\0' 结束符(比如错误输入),则遍历会一直进行,可能导致 originalLength 超过 len - 1,但这种情况属于非法输入(函数会通过后续的 newLength 检查避免越界)。

总结:

originalLength字符串实际有效字符的总数(含空格,不含 '\0',而 len - 1缓冲区理论上能容纳的最大有效字符数。二者是否相等,取决于字符串是否填满了整个缓冲区。
在正常情况下(字符串有正确的 '\0' 且未超出缓冲区),originalLength 一定 小于或等于 len - 1,但不一定等于 len - 1

http://www.lryc.cn/news/623726.html

相关文章:

  • Java注解学习记录
  • 26. 值传递和引用传递的区别的什么?为什么说Java中只有值传递
  • 大模型对齐算法合集(一)
  • Zemax 中的透镜设计 - 像差理论
  • 评测系统构建
  • 深入分析 Linux PCI Express 子系统
  • 计算机网络 TCP time_wait 状态 详解
  • 10 SQL进阶-SQL优化(8.15)
  • Matlab课程实践——基于MATLAB设计的计算器软件(简单、科学、电工、矩阵及贷款计算)
  • esp32(自定义分区)coredump
  • C语言私人学习笔记分享
  • 关于第一次接触Linux TCP/IP网络相关项目
  • 使用Ansys Fluent进行倒装芯片封装Theta-JA热阻表征
  • 计算机网络 OSI 七层模型和 TCP 五层模型
  • IP 分片和组装的具体过程
  • 数字货币的法律属性与监管完善路径探析
  • Trae 辅助下的 uni-app 跨端小程序工程化开发实践分享
  • 【Java后端】Spring Boot 集成 MyBatis-Plus 全攻略
  • 【昇腾】单张48G Atlas 300I Duo推理卡MindIE+WebUI方式跑14B大语言模型_20250817
  • 前端vue3+后端spring boot导出数据
  • Java 大视界 -- Java 大数据分布式计算在基因测序数据分析与精准医疗中的应用(400)
  • Linux | i.MX6ULL网络通信-套字节 UDP(第十八章)
  • 计算机网络 TCP 延迟确认机制
  • 矿物分类案列 (一)六种方法对数据的填充
  • 安卓开发者自学鸿蒙开发2页面高级技巧
  • 安卓14系统应用收不到开机广播
  • Android原生(Kotlin)与Flutter混合开发 - 设备控制与状态同步解决方案
  • Javascript面试题及详细答案150道之(106-120)
  • Python实现区域生长和RANSAC聚类
  • 职场新人如何在快速适应工作的同时保持自我成长节奏?