数据结构与算法--字符串:字符串替换

数据结构与算法–字符串:字符串替换

字符串的优化
  • 由于字符串在编程时候使用的评率非常高,为了优化,很多语言都对字符串做了特殊的规定。下面我们讨论java中字符串的特性
  • java中的字符数组以’\0’ 结尾,我们可以利用这个特性来找到字符数组的末尾,而且为了节省内存,java将常量字符串存储在单独的一个区域,我们新申请内存的字符串存储在另外一个地方,总结就是:基本类型的的变量数据和对象的引用都是在栈里面,对象本身放再堆里面,显示的String常量放再常量池,String对象放再堆中。
常量池说明
  • 常量池之前是放在方法区里面,也就是永久代,从JDK1.7开始移动到堆内存中,这个改变我们可以从oracle的 release version里面的 Important RFEs Addressed in JDK 7 看到
Area: HotSpotSynopsis: In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences.
String内存位置说明
  • 显示的声明String常量
String str1 = new String("hello world");
String str2 = "hello world";
  • 以上第一句执行后会在堆内存中创建了一个值为hello world的String对象,同时会在产量池中创建一个值为hello world的String对象
  • 第二句执行的时候因为产量池中存在hello world对象,就不会再常量池中创建了hello world的String对象,直接栈中创建str2,并指向产量池中的hello world 地址。
  • 验证以上:
public class StrSaveAddress {private static final String str3 = "hello world";public static void strMain() {String str1 = new String("hello world");String str2 = "hello world";String str4 = str3;String str5 = str3;System.out.println(str1 == str2);System.out.println(str1.equals(str2));System.out.println(str4 == str5);}public static void main(String[] args) {strMain();}
}
//输出:
false
true
true
  • Java中的String类型比较特殊,修饰如下 public final class String,显然是不可改变的,所有我们每一次对String内容的我修改都会产生一个新的实例,比如toUpperCase转大写操作,这个操作的结果是生成一个新的String实例并且返回,原String内容并不会修改,因此原来的Hello world还是愿意的值。由此可见试图对String多次修改,每次都会有一个临时对象。这样开销大,内存浪费太多。java中定义了一个新的与字符串相关的StringBuilder,能容纳修改后的结果英雌如果多次修改,建议用StringBuilder。

算法题:替换空格

  • 题目:请事先一个函数,吧字符串中的每个空格替换成%20,例如输出“this is my way”,则输出“this%20is%20my%20way ”
  • 网络编程中,URL参数含义特殊字符,如空格,‘#’等,可能导致服务器无法获取正确的参数。我们将这些特殊符号换成服务器可识别的字符。转换后的规则就是在‘%’后面加上ASCII码的两位十六进制标识。比如空格的ASCII码是32,那么转16进制是0x20,因此我们得到的被替换的空格就是‘%20’ 这个是浏览器URL中%20的由来,再比如’#'的ASCII是35,十六进制是0x23,那么URL中就是%23
  • 分析:题中需要将空格替换成‘%20’ 一个字符变三个字符,长度改变,如果在原字符上修改,必定会覆盖改字符串之后的数据。如果创建新的字符串,并在新字符串上修改替换,那么我们可以自己分配足够的内存,有这两种解决方法一下我们两种方法都来尝试解答。
原字符串修改,时间复杂度O(n^2) ,空间复杂度O(1)
  • 最简单的做法,循环遍历字符串,每次碰到空格先移动之后字符,在替换空格。如下图流程:

  • 第一步找到第一个空格
    在这里插入图片描述

  • 第二步,移动空格之后的字符2个位置,将空格替换成%20,彩色部分是需要移动的字符
    在这里插入图片描述

  • 第三步之后的流程和第二步一样,一直到遍历到最后一个字符

  • 实现方式如下:

/*** @author liaojiamin* @Date:Created in 18:30 2020/11/3*/
public class StrReplace {/*** 时间复杂度O(n^2) 空间复杂度O(1)* @return a* @author: liaojiamin* @Param str 目标字符串 '\0' 结束* @Param replaceChar 需替换字符* @param length 字符数组总长度* @date: 19:10 2020/11/3*/public static char[] replaceFirst1(char[] str, char replaceChar, int length){if(null == str || str.length == 0){return str;}int originStrLength = 0;int replaceStrCount = 0;while(str[originStrLength] != '\0'){originStrLength ++;if(str[originStrLength] == replaceChar){replaceStrCount ++;}}int resultLength = replaceStrCount * 2 + originStrLength;if(resultLength > length){return str;}int i = 0;while(str[i] != '\0'){if(str[i] == ' '){for (int j = originStrLength; j > i; j--) {str[j+2] = str[j];}str[i++] = '%';str[i++] = '2';str[i++] = '0';originStrLength+=2;}else {i++;}}return str;}public static char[] getChar(String str, int length){char[] chars = new char[length];for (int i = 0; i < str.length(); i++) {chars[i] = str.charAt(i);}return chars;}public static void main(String[] args) {int length = 100;char[] chars = getChar("this is my way", length);System.out.println(new String(replaceFirst1(chars, ' ', length)));}
新字符串中修改,时间复杂度O(n) ,空间复杂度O(n)
  • 新字符串中修改,也不难,我们先计算好替换之后需要的总内存,新申请合适的内存,接着只需要将老的字符串一个一个复制进去,碰到空格则替换成%20替换即可。如下步骤

  • 第一步申请新的字符串计算结果长度,申请新容量内存空间
    在这里插入图片描述

  • 需要的总内存
    在这里插入图片描述

  • 第二步:遍历字符串,将字符按位复制进新的内存位置中,如果遇到空格则复制20%,如下图:
    在这里插入图片描述

  • 第三部:依第二步骤继续复制,得到最终结果:

  • 实现方式如下:

public class StrReplace {/*** 时间复杂度O(n) 空间复杂度O(n)* @return a* @author: liaojiamin* @Param str 目标字符串 '\0' 结束* @Param replaceChar 需替换字符* @param length 字符数组总长度* @date: 19:10 2020/11/3*/public static char[] replaceFirst(char[] str, char replaceChar, int length){if(null == str || str.length == 0){return str;}int countStrNum = 0;int i = 0;while(str[i] != '\0'){i++;if(str[i] == replaceChar){countStrNum ++;}}if(countStrNum == 0){return str;}int resultStrLength = i + countStrNum*2;if(resultStrLength > length){return str;}char[] resultStr = new char[resultStrLength];int resultPosition = 0;for (int j = 0; j < i; j++) {char thisChar = str[j];if(thisChar == replaceChar){resultStr[resultPosition++]='%';resultStr[resultPosition++]='2';resultStr[resultPosition++]='0';}else {resultStr[resultPosition++] = thisChar;}}return resultStr;}public static char[] getChar(String str, int length){char[] chars = new char[length];for (int i = 0; i < str.length(); i++) {chars[i] = str.charAt(i);}return chars;}public static void main(String[] args) {int length = 100;char[] chars = getChar("this is my way", length);System.out.println(new String(replaceFirst(chars, ' ', length)));}
}
最优解,原字符串中修改,时间复杂度O(n) ,空间复杂度O(1)
  • 第一种思路中每遇到空格都会需要移动之后的字符,导致时间复杂度指数增加,我们需要更快捷的方法
  • 依照以上的思路,我们先遍历一次字符串,统计空格数,计算最终大小,每个空格增加2位置,第一种思路是从头开始变量,导致位置的移动频繁,
  • 我们从字符串最后开始复制替换,首先追捕我们需要准备两个指针,一个用来指向最终替换之后的位置,一个指向原来位置,如下图,s1指定字符串原来的长度末尾,s2指定替换之后的长度末尾。
    在这里插入图片描述
  • 我们从S1 处开始遍历,每遇到一个字符将S1位置复制到S2 位置,如果遇到空格则S2 位置复制%20三个字符如下图替换一个之后的效果图,遇到空格后,s2同时移动三个位置,s1 永远都移动一个位置:
    在这里插入图片描述
  • 最后持续依据第二步替换,得到最终结果,实现方式如下:
public class StrReplace {/*** 最优解:时间复杂度O(n) 空间复杂度O(1)* @return a* @author: liaojiamin* @Param str 目标字符串 '\0' 结束* @Param replaceChar 需替换字符* @param length 字符数组总长度* @date: 19:10 2020/11/3*/public static char[] replaceFirst2(char[] str, char replaceChar, int length){if(null == str || str.length == 0){return str;}int originStrLength = 0;int replaceStrCount = 0;while(str[originStrLength] != '\0'){originStrLength ++;if(str[originStrLength] == replaceChar){replaceStrCount ++;}}int resultLength = replaceStrCount * 2 + originStrLength;if(resultLength > length){return str;}int i = resultLength;while (originStrLength >= 0){if(str[originStrLength] == ' '){str[i--] = '0';str[i--] = '2';str[i--] = '%';}else {str[i--] = str[originStrLength];}originStrLength --;}return str;}public static char[] getChar(String str, int length){char[] chars = new char[length];for (int i = 0; i < str.length(); i++) {chars[i] = str.charAt(i);}return chars;}public static void main(String[] args) {int length = 100;char[] chars = getChar("this is my way", length);System.out.println(new String(replaceFirst2(chars, ' ', length)));}
}
测试用例
  • 输入字符中包含空格
  • 输入字符中没有空格
  • 输入特殊字符:null

相关算法题型

  • 题目:有两个排序的数组A1和A2,内存在A1 的末尾有足够多的空余空间容纳A2.实现一个函数,将A2中所有数组插入到A1 中并且所有数组是排序状态:
  • 分析:同之前的思路,我们开始看到题目可能从头遍历A1 ,当这样会出现多次复制数字的情况。更好的方法是计算A1+A2总长度后,逐个比较A1,A2末尾数字,并将末尾数字大的放到指定的位置。算法实现如下:
public class SplicStr {/*** 时间复杂度O(n),空间复杂度O(1)* @author: liaojiamin* @description: 拼接两个排序的数组,target 足够容纳两个数组* @date: 14:54 2020/11/4** @return */public static int[] splicStr(int[] target, int[] origin){if(null == target || null == origin){return null;}int targetLength = 0;while (target[targetLength] != '\0'){targetLength ++;}int resultLength = targetLength + origin.length;if(target.length < resultLength){return null;}int resultPosition = resultLength - 1;int targetPosition = targetLength - 1;int originPosition = origin.length - 1;while (resultPosition >= 0){if(targetPosition >= 0 && originPosition >= 0&& target[targetPosition] > origin[originPosition]){target[resultPosition--] = target[targetPosition--];}else {target[resultPosition--] = origin[originPosition--];}}return target;}public static void main(String[] args) {int[] target = new int[20];int[] origin = new int[10];for (int i = 0; i < 10; i++) {target[i] = i+300;origin[i] = i*10;}int[] targetInt = splicStr(target, origin);for (int i : targetInt) {System.out.print(i + " ");}}
}

上一篇:数据结构与算法–数组:二维数组中查找
下一篇:数据结构与算法–排序算法总结

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

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

相关文章

数据结构与算法--经典10大排序算法(动图演示)【建议收藏】

十大经典排序算法总结&#xff08;动图演示&#xff09; 算法分类 十大常见排序算法可分为两大类&#xff1a; 比较排序算法&#xff1a;通过比较来决定元素的位置&#xff0c;由于时间复杂度不能突破O(nlogn)&#xff0c;因此也称为非线性时间比较类排序非比较类型排序&…

如何查找,修复和避免C#.NET中内存泄漏的8个最佳实践

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。本文来源&#xff1a;https://michaelscodingspot.com/find-fix-and-avoid-memory-leaks-in-c-net-8-best-practices/从事大型企业项目的任何人都知道内存泄漏就像是大型酒店中的老鼠。当它们很少时&#xff0c;您可能不会…

ASP.NET Core技术研究-探秘依赖注入框架

ASP.NET Core在底层内置了一个依赖注入框架&#xff0c;通过依赖注入的方式注册服务、提供服务。依赖注入不仅服务于ASP.NET Core自身&#xff0c;同时也是应用程序的服务提供者。毫不夸张的说&#xff0c;ASP.NET Core通过依赖注入实现了各种服务对象的注册和创建&#xff0c;…

Redis遍历方式思考--字典扩容方式

全量遍历keys 工作中线上Redis维护&#xff0c;有时候我们需要查询特定前缀的缓存key列表来手动处理数据。可能是修改值&#xff0c;删除key。那么怎么才能快速的从海量的key中查找到对应的前缀匹配项。Redis提供了一下简单的指令&#xff0c;例如keys用来满足特定正则下的key…

从项目到产品: 软件时代需要价值流架构师 | IDCF

译者&#xff1a;无敌哥原文地址: https://thenewstack.io/the-age-of-software-needs-value-stream-architects/ 本文翻译仅供学习交流之用。原文作者 Mik Kersten 出版了《Project to Product》本系列共四篇文章&#xff0c;分别是01 从项目到产品&#xff1a;软件需要从物理…

Redis高效性探索--线程IO模型,通信协议

Redis线程IO模型 Redis是单线程&#xff0c;这个毋庸置疑Redis单线程能做到这么高的效率&#xff1f;不用怀疑&#xff0c;还有很多其他的服务都是单线程但是也有超高的效率&#xff0c;比如Node.js&#xff0c;Nginx也是单线程。Redis单线程高效原因&#xff1a; Redis所有数…

Redis持久化-深入理解AOF,RDB

持久化 Redis数据全部在内存中&#xff0c;如果宕机&#xff0c;数据必然丢失&#xff0c;因此必须有一种机制保证Redis数据不会因为故障丢失&#xff0c;这就是Redis的持久化机制持久化方式两种&#xff1a;AOF&#xff0c;RDB&#xff0c;如下图 RDB快照模式是一次全量备份&…

推荐一个集录屏、截图、音频于一体的软件给大家

捕获屏幕&#xff0c;网络摄像头&#xff0c;音频&#xff0c;光标&#xff0c;鼠标单击和击键GitHub&#xff1a;https://github.com/MathewSachin/Captura特性 免费 100%免费&#xff0c;你不需要花一分钱开源 根据MIT许可的条款&#xff0c;可以在Github上获得Captura的源…

Redis高效性探索--管道

管道 开始接触Redis时候&#xff0c;对应Redis管道有一个错误认识&#xff0c;任务是redis服务器提供的一种特别的技术&#xff0c;有了这种技术可以加速Redis的存取效率&#xff0c;但是实际上Redis的管道计算&#xff08;Pipeline&#xff09;本身是客户端提供的技术&#x…

Redis--事务理解

事务 一个成熟的数据库系统一般都会有事务的支持&#xff0c;Redis作为一个缓存数据库也不例外&#xff0c;Redis的事务比之关系型数据库mysql&#xff0c;oracle等算比较简单的&#xff0c;Redis中无需理解那么多事务模型&#xff0c;可以直接使用。不过也正是因为简单&#…

.NET中的内存管理

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。 .NET中的内存管理资源分配Microsoft .NET公共语言运行时要求从托管堆分配所有资源。当应用程序不再需要对象时&#xff0c;它们将自动释放。初始化进程后&#xff0c;运行时将保留地址空间的连续区域&#xff0c;该区域最…

Redis存储优化--小对象压缩

小对象压缩 Redis是一种内存数据库&#xff0c;内存是计算机中一种比较宝贵的资源&#xff0c;如果我们不注意节约&#xff0c;Redis很可能出现内存不足&#xff0c;最终导致崩溃。Redis为了优化数据结构的内存占用&#xff0c;增加了非常多的优化点&#xff0c;这些优化也是牺…

.Net微服务实战之技术架构分层篇

一拍即合上一篇《.Net微服务实战之技术选型篇》&#xff0c;从技术选型角度讲解了微服务实施的中间件的选择与协作&#xff0c;工欲善其事&#xff0c;必先利其器&#xff0c;中间件的选择是作为微服务的基础与开始&#xff0c;也希望给一直想在.Net入门微服务的同行有一个很好…

Redis高可用基石--主从同步

主从同步 当我们将Redis用于线上环境&#xff0c;单机肯定是不行的&#xff0c;即使不做集群&#xff0c;我们也应该做主从&#xff0c;有了主从&#xff0c;当主节点&#xff08;master&#xff09;挂掉时候&#xff0c;让运维将从节点&#xff08;slave&#xff09;接管&…

.NET 下基于动态代理的 AOP 框架实现揭秘

.NET 下基于动态代理的 AOP 框架实现揭秘Intro之前基于 Roslyn 实现了一个简单的条件解析引擎&#xff0c;想了解的可以看这篇文章 基于 Roslyn 实现一个简单的条件解析引擎执行过程中会根据条件的不同会在运行时创建一个类&#xff0c;每一次创建都会生成一个新的程序集&#…

C++实现链式基数排序

代码如下: #include <iostream> #include <cmath> using namespace std; typedef int KeyType; const int END -1; const int Radix 10;typedef struct Node {KeyType key;struct Node *next; };Node *CreateList() {KeyType x;Node *q nullptr;cin >> x…

Blazor WebAssembly 3.2.0 Preview 4 如期发布

ASP.NET团队如期3.16在官方博客发布了 Blazor WebAssembly 3.2.0 Preview 4&#xff1a;https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-preview-4-release-now-available/ &#xff0c;同时在twitter上发了一条信息带上了下面这张图&#xff0c;这张图很形象…

C#/.Net Core/WPF框架初建(国际化、主题色)

English | 简体中文作为 TerminalMACS 的一个子进程模块 - WPF管理端&#xff0c;目前搭建框架部分功能&#xff1a;本地化、国际化、主题色修改等。导航目录1.框架已添加功能说明1.1. 国际化、本地化1.2. Metro风格主窗体1.3. 动态更换主题色2.关于TerminalMACS及本WPF管理端 …

Redis底层实现--字符串

Redis字符串存储实现原理 Redis 中的字符串是可以修改的字符串&#xff0c;在内存中他是以字节数组的形式存在的。我们在入门语言C语言里面的字符串标准形式是以NULL&#xff08;即0x\0&#xff09;作为结束符&#xff0c;但是Redis里面&#xff0c;字符串表示方法不是这样&am…

[C++STL]C++实现string容器

代码如下: #pragma once #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <assert.h> #include <cstring> using namespace std;class String { public:String(const char *str ""){assert(str ! nullptr);_size strlen(str);_s…