剑指offer --- 字符串中替换空格

news2024/9/21 12:46:54

目录

前言

一、读懂题目

二、思路分析

三、代码呈现

总结


前言

        作为引入讲讲这道题的由来,在网络编程中,如果 URL 参数中含有特殊字符,如空格、#等,可能导致服务器端无法获得正确的参数值。我们需要将这些特殊符号转换成服务器可以识别的字符。转换的规则是在%后面跟上 ASCI 码的两位十六进制的表示。比如空格的 ASCII 码是 32,即十六进制的 0x20,因此空格被替换成"%20"。


一、读懂题目

请实现一个函数,把字符串中的每个空格替换成"%20"。

例如输入“We are happy.”,则输出“We%20are%20happy.”

        首先我们明白题目要求我们提供一个功能可用于替换空格为“%20”,那么该函数的传入参数应该是字符串类型(char* 或 string),同时因为涉及到字符串修改操作,就必须将参数列表中负责接收字符串部分设定为非const类型。

二、思路分析

        原来一个空格字符,替换之后变成 “%20” 这 3 个字符,因此字符串会变长。如果是在原来的字符串上做替换,那么就有可能覆盖修改在该字符串后面的内存。如果是创建新的字符串并在新的字符串上做替换,那么我们可以自己分配足够多的内存。如果在允许新创建字符串时我们可以遍历整个字符串,当遇到空格字符时利用%20进行替换。如果不允许,我们就要求原字符串有足够的空间容纳替换后变长的字符串。此处我们实现不利用额外空间(创建新字符串)的情况下进行字符替换

        不妨我们在功能性测试中取字符串 “we are happy.” 进行分析,当我们从左往右遍历,遇见空格就将其换为%,同时后面所有字符向后移动两位,将移位后空开的部分填入 ’2‘ ’0‘ 两个字符,继续遍历,当遇到新空格时,重复上面过程即可,如下图:

我们注意到字符串“happy”在此次操作中一共移动了两次,当字符串很长,空格很多的情况下,这种方式明显在执行效率上有明显缺陷,假设字符串的长度是 n。对每个空格字符,需要移动后面 O (n) 个字符,因此对含有 O (n) 个空格字符的字符串而言总的时间效率是 O (_{N}2),那有没有什么办法可以减少移动次数甚至一步到位呢?

        自然而然地我们想到,如果采用从后向前的遍历方式,把那些需要移动多次的字符直接一步到位地安置在插入后地位置,所以当我们需要置定最后一个字符位置时,我们可以先遍历一次字符串,这样就能统计出字符串中空格的总数,并可以由此计算出替换之后的字符串的总长度。每替换一个空格,长度增加 2,因此替换以后字符串的长度等于原来的长度加上 2 乘以空格数目。我们还是以前面的字符串"we are happy."为例,"we are happy."这个字符串的长度是14(包括结尾符号 \0),里面有两个空格,因此替换之后字符串的长度是18。(这里仅仅用该字符串做演示,其他字符串同理)

        接着我们定义两个指针p1, p2分别指向源字符串末尾和替换后最后一个字符应该所在的位置。利用从后向前遍历的方式,如果p1指向的字符不为空格,将p1指针指向的值赋给p2;如果p1指向的字符是空格,那么p1向左一格,p2共计向左三格,p2左移过程中分别赋值 '0', '2', '%' 即可。至此我们已经理清了重要的执行步骤,接着我们只需要判断该步骤在何时终止并返回结果。

        当我们定义的p1, p2指针指向同一位置时,代表左侧字符串中不可能再有空格出现,即替换操作处理完毕。或者我们可以加入计数器,每当p1找到一次空格,计数器减一当等于0时,表示所有空格已被替换。

三、代码呈现

C语言风格:

char* replaceBlank(char* str, size_t length, size_t capacity)
{
	assert(str && length != 0);

	const char* p1 = (str + length);
	const char* index = p1;
	int blank_num = 0;
	while (index != str)
	{
		// 注意这里解引用访问字符时,需要先减后访问  类似于a[n]的数组访问最后一位元素应该是*(a+n-1)或a[n-1]
		if (*--index == ' ') { blank_num++; }
	}

	char* p2 = str + (length + blank_num * 2);
	if (p2 >= str + capacity) { perror("string length is not long enough to replace!"); return NULL; }    // 字符串初始化定义空间不足
	while (p1 != p2)
	{
		if (*p1 != ' ') { *p2-- = *p1--; }
		else
		{
			// 一比三的占比向左移动两指针
			*p2-- = '0';
			*p2-- = '2';
			*p2-- = '%';
			p1--;
		}
	}
	return str;
}

测试代码:

1)基础测试

void test1()
{
	char str[30] = "we are happy.";
	size_t size = strlen(str);
	replaceBlank(str, size, 30);
	printf("%s\n", str);
}

运行结果:

 2)鲁棒性测试

void test1_1()
{
	// 测试空指针传入
	char* str1 = nullptr;
	size_t size1 = 5;
	replaceBlank(str1, size1, 30);
	printf("%s\n", str1);

	// 测试 length 不符合要求
	char str2[30] = "we are happy.";
	size_t size2 = 0;
	replaceBlank(str2, size2, 30);
	printf("%s\n", str2);

	//测试初始化空间不够容纳替换后的字符串
	char str3[15] = "we are happy.";
	size_t size3 = strlen(str3);
	replaceBlank(str3, size3, 15);
	printf("%s\n", str3);
}

由于会发生中断导致后面代码不能正常执行,所以将三种情况分别测试时注释掉另外两个测试段,运行结果如下:

1,2代码段都会发生中断以反映程序中功能函数参数存在错误:

3号代码运行结果:

因为有perror函数的存在,在运行窗口我们可以得知程序在哪方面是存在问题的,最后输出原字符串是因为我们在实现逻辑中如果抛出第一行错误就立即返回原字符串,避免程序继续向下执行。

 3)边界测试

void test1_2()
{
	// 测试传入字符串全为空格
	char str4[30] = "       ";
	size_t size4 = strlen(str4);
	replaceBlank(str4, size4, 30);
	printf("%s\n", str4);

	// 测试传入字符串仅有单个空格
	char str5[30] = " ";
	size_t size5 = strlen(str5);
	replaceBlank(str5, size5, 30);
	printf("%s\n", str5);
}

运行结果:

C++风格:

两者重要步骤代码没什么不同,只是改变了函数异常处理模块,使得外部处理检查更精确。

先附上C++常用异常类:

  1. std::exception: 所有标准异常类的基类。
  2. std::bad_alloc: 内存分配失败时抛出的异常。
  3. std::bad_cast: 当dynamic_cast无法将指针或引用转换为目标类型时抛出的异常。
  4. std::bad_exception: 在异常处理过程中抛出的异常。
  5. std::bad_typeidtypeid操作符无法处理的类型时抛出的异常。
  6. std::logic_error: 逻辑错误相关的异常基类。
    • std::invalid_argument: 当函数参数的值不合适时抛出的异常。
    • std::domain_error: 当参数的值导致函数域错误时抛出的异常。
    • std::length_error: 当对象的长度超过其最大限制时抛出的异常。
    • std::out_of_range: 当访问超出有效范围的对象时抛出的异常。
  7. std::runtime_error: 运行时错误相关的异常基类。
    • std::range_error: 当参数的值超出有效范围时抛出的异常。
    • std::overflow_error: 当进行数值溢出操作时抛出的异常。
    • std::underflow_error: 当进行数值下溢操作时抛出的异常。
  8. std::system_error: 与底层操作系统或库函数相关的异常基类。
  9. std::ios_base::failure: I/O 操作失败时抛出的异常。

 代码如下:

char* replaceBlank_Cpp(char* str, size_t length, size_t capacity)   // 可以将参数列表换为string类对象,这样可以仅传一个参数
{
	if (str == nullptr)
	{
		throw invalid_argument("str is nullptr!");
	}
	if (length <= 0)   // 在某些平台上,可能存在将 size_t 作为有符号类型处理的情况
	{
		throw invalid_argument("length is not a positive interger!");      // 非正整数
	}

	const char* p1 = (str + length);
	const char* index = p1;
	size_t blank_num = 0;
	while (index != str)
	{
		// 注意这里解引用访问字符时,需要先减后访问  类似于a[n]的数组访问最后一位元素应该是*(a+n-1)或a[n-1]
		if (*--index == ' ') { blank_num++; }
	}

	char* p2 = str + (length + blank_num * 2);
	if (p2 >= str + capacity) { throw runtime_error("string length is not long enough to replace!"); return str; }    // 字符串初始化定义空间不足
	while (p1 != p2)
	{
		if (*p1 != ' ') { *p2-- = *p1--; }
		else
		{
			// 一比三的占比向左移动两指针
			*p2-- = '0';
			*p2-- = '2';
			*p2-- = '%';
			p1--;
		}
	}
	return str;
}

测试代码:

1)基础测试

void test2()
{
	char str[30] = "we are happy.";
	size_t size = strlen(str);
	replaceBlank_Cpp(str, size, 30);
	cout << str << endl;
}

运行结果:

2)鲁棒性测试和边界测试

由于C++中的异常捕获使得我们在接收到错误信息后可以展示在屏幕上,不会中断整个程序的运行,所以这里将鲁棒性测试一同进行:

void test2_1()      // 鲁棒性测试
{
	// 测试空指针传入
	try
	{
		char* str1 = nullptr;
		size_t size = 5;
		replaceBlank_Cpp(str1, size, 30);
	}
	catch (invalid_argument& e)
	{
		cout << e.what() << endl;
	}
	catch (runtime_error& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "Other default!" << endl;
	}

	// 测试 length 不符合要求
	try
	{
		char str2[30] = "we are happy.";
		size_t size = 0;
		replaceBlank_Cpp(str2, size, 30);
	}
	catch (invalid_argument& e)
	{
		cout << e.what() << endl;
	}
	catch (runtime_error& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "Other default!" << endl;
	}

	//测试初始化空间不够容纳替换后的字符串
	try
	{
		char str3[15] = "we are happy.";
		size_t size = strlen(str3);
		replaceBlank_Cpp(str3, size, 15);
	}
	catch (invalid_argument& e)
	{
		cout << e.what() << endl;
	}
	catch (runtime_error& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "Other default!" << endl;
	}


	// 测试传入字符串全为空格
	char str4[30] = "       ";
	size_t size4 = strlen(str4);
	replaceBlank_Cpp(str4, size4, 30);
	cout << str4 << endl;
	// 测试传入字符串仅有单个空格
	char str5[30] = " ";
	size_t size5 = strlen(str5);
	replaceBlank_Cpp(str5, size5, 30);
	cout << str5 << endl;
}

运行结果:


总结

        在处理替换问题时,该问题完全可以拓展到数组中在指定位置插入新值时,后面所有元素对应都要后移,为了避免覆盖或创建额外空间,我们只好从后往前遍历实现逐个覆盖,最终实现插入或者长度变宽的修改。在编程解决问题甚至构建一个小功能时都要尽可能考虑到所有的边界情况和鲁棒性检查,只有这样才能近乎完美的解决遇到的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1153374.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式04———桥接模式 c#

桥接模式&#xff1a;将一个事物从多个维度抽象出来&#xff0c;采用 分离 和 组合 的方式 替代 原本类的继承 桥接模式&#xff08;Bridge Pattern&#xff09;是一种软件设计模式&#xff0c;属于结构型模式&#xff0c;它用于将抽象部分与具体实现部分分离&#xff0c;以便它…

SpringCloud(三) Ribbon负载均衡

SpringCloud(二) Eureka注册中心的使用-CSDN博客 在SpringCloud(二)中学习了如何通过Eureka实现服务的注册和发送,从而通过RestTemplate实现不同微服务之间的调用,加上LoadBalance注解之后实现负载均衡,那负载均衡的原理是什么呢? 目录 一, 负载均衡 1.1 负载均衡原理 1.2 源…

怎么用python代码查看可用的gpu,然后指定可用的gpu运行

查看gpu import torch# 检查CUDA是否可用 if torch.cuda.is_available():# 获取GPU设备数量device_count torch.cuda.device_count()# 列出可用的GPU设备for i in range(device_count):print(f"GPU {i}: {torch.cuda.get_device_name(i)}") else:print("CUDA …

JZ22链表中倒数最后k个节点

JZ22链表中倒数最后k个节点 解法一 双指针法&#xff0c;定义两个快慢指针&#xff0c;先让快指针走k步&#xff0c;然后让两个指针以相同速度向前&#xff0c;直到快指针走到尾部none的位置&#xff0c;此时慢指针正好在倒数第k的位置此方法的时间复杂度为O&#xff08;n&…

Ubuntu Desktop 20.04升级gcc-11

默认自带的gcc是9&#xff0c;需要升级到11 sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt update sudo apt install gcc-11 sudo apt install g11 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 50 sudo update-alternatives -…

数字孪生三剑客!云渲染及虚拟仿真服务器产品MapGIS Server for Unreal来了

10月18日&#xff0c;新一代全空间智能GIS平台的升级之作——MapGIS 10.6 Pro在深圳正式发布&#xff0c;整场发布会全空间GIS、智能GIS、信创GIS、全栈开发各维度创新一一揭晓&#xff0c;全空间一体化、深度融合云端的全系产品全面升级&#xff0c;科技盛宴、高潮迭起。其中尤…

海外跨境电商云厂商阿里云、华为云、九河云选择攻略

近几年出海已成为趋势&#xff0c;而要打有准备的仗&#xff0c;就必须先收集关键信息。国内企业出海需要对海外市场进行深入的分析和挖掘&#xff0c;数据分析技术可以帮助国内企业更好地了解海外市场的需求和趋势&#xff0c;包括市场调研、用户画像、销售数据分析等方面&…

UnrealSynth虚幻合成数据生成器

UnrealSynth 虚幻合成数据生成器利用虚幻引擎的实时渲染能力搭建逼真的三维场景&#xff0c;为 YOLO 等 AI 模型的训练提供自动生成的图像和标注数据。UnrealSynth 生成的合成数据可用于深度学习模型的训练和验证&#xff0c;可以极大地提高各种行业细分场景中目标识别任务的实…

RCD负载箱的价格和性价比如何?

RCD负载箱用于测试电力设备的专用设备&#xff0c;主要用于检测电路的电流、电压和功率等参数。 RCD负载箱的价格与其品牌、型号、功能和性能有关&#xff0c;知名品牌和高性能的RCD负载箱价格会相对较高&#xff0c;而一些普通品牌或功能较简单的RCD负载箱价格则相对较低&…

使用 :has() 选择前一个相邻元素

使用 CSS :has() 选择前一个兄弟姐妹 CSS 更令人抓狂的限制之一是长期以来它无法根据其子元素或前一个兄弟元素来选择元素。这使得构建可以针对元素的先前同级元素的 CSS 选择器变得不可能&#xff0c;但是has:()伪类&#xff08;以及来自选择器级别 4 的、 和&#xff09;已经…

【C++入门 二 】学习使用C++命名空间及其展开

C入门 二 1. C第一个小程序2.命名空间2.1命名冲突2.2 命名空间2.2.1 命名空间定义2.2.2.命名空间的展开方式1&#xff08;直接展开&#xff09;方式2&#xff08;指定展开&#xff09; 3.C输入&输出①输出②输入 1. C第一个小程序 下面这段代码有两个特性 1、io流 2、命名空…

innovus:如何解决editAddRoute不能在平行线间打孔的问题

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 用户使用editAddRoute命令或者图形界面手动拉线,默认只能在相同net 的不同层net shape crossover(交叉)位置上打孔,如图右侧GNDD交叉的位置有打孔,而左侧平行的两条net shape无法自动打孔。 有…

众和策略:鸿蒙4升级设备数量破亿;财政部调整保险公司考核方式

重要的消息有哪些 财政部调整保险公司查核办法 增强险资入市积极性 财政部10月30日消息显现&#xff0c;近日已印发《关于引导保险资金长时间稳健投资、调整国有商业保险公司绩效评价相关指标的告诉》&#xff08;下称《告诉》&#xff09;&#xff0c;清晰将国有商业保险公司…

​CRM中的大客户销售是什么?​

对企业来说&#xff0c;大客户可能贡献了大部分的销售业绩。什么样的客户可以被认定为是大客户&#xff1f;大客户销售与普通销售有何区别&#xff1f;针对大客户又该采取什么样的销售策略呢&#xff1f;从回答这几个问题开始&#xff0c;我们来说说CRM中的大客户销售是什么&am…

数智领航未来∣2023契约锁企企通专题沙龙:数字建设,为企业高质量发展注入新动能

今年两会期间&#xff0c;“数字经济”是当之无愧的热词之一。中央经济工作会议提出&#xff0c;要大力发展数字经济&#xff0c;提升常态化监管水平&#xff0c;支持平台经济发展。 当前数据作为新型生产要素&#xff0c;价值尚未得到充分释放&#xff0c;需要疏通数据要素流通…

Microsoft 365 管理自动化

Microsoft 365 服务被大多数组织广泛使用&#xff0c;每天生成的数据量巨大。解决 Microsoft 365 中的问题可能非常困难&#xff0c;并且使用多个管理中心来保护组织变得复杂。本机控制台还缺少某些批量管理任务、全面的审计报告和基于角色的精细访问控制。 Microsoft 360 管理…

2、NLP文本预处理技术:词干提取和词形还原

一、说明 在上一篇文章中&#xff0c;我们解释了文本预处理的重要性&#xff0c;并解释了一些文本预处理技术。在本文中&#xff0c;我们将介绍词干提取和词形还原主题。 词干提取和词形还原是两种文本预处理技术&#xff0c;用于将单词还原为其基本形式或词根形式。这些技术的…

OpenGL ES相关库加载3D 车辆模型

需求类似奇瑞的这个效果&#xff0c;就是能全方位旋转拖拽看车&#xff0c;以及点击开关车门车窗后备箱等 瑞虎9全景看车 (chery.cn) 最开始收到这个需求的时候还有点无所适从&#xff0c;因为以前没有做过类似的效果&#xff0c;后面一经搜索后发现实现的方式五花八门&#xf…

十月听书笔记

十月听书笔记 1、《宝贵的人生建议》2、《后工作时代》3、《我们何以不同》4、《恰到好处的帮助》5、《24个比利》6、《恰到好处的安慰》7、《为什么越无知的人越自信》8、《陌生人效应》9、《往里走&#xff0c;安顿自己》10、《为什么学生不喜欢上学》11、《自律力》12、《弹…

浅析Redis大Key | 京东云技术团队

一、背景 在京东到家购物车系统中&#xff0c;用户基于门店能够对商品进行加车操作。用户与门店商品使用Redis的Hash类型存储&#xff0c;如下代码块所示。不知细心的你有没有发现&#xff0c;如果单门店加车商品过多&#xff0c;或者门店过多时&#xff0c;此Key就会越来越大…