⭐️引言⭐️
大家好啊,我是执梗。今天为大家带来一套字符串处理的专题训练。字符串这东西大家无论是学习还是工作中,接触的肯定不少,平时如果对字符串处理,大家肯定都是直接使用库函数。毕竟字符串的库函数,那可是多了去了,像Java和Python这种语言,只要你能想到的操作基本都帮你封装好了。但是这就容易让人养成了不好的习惯,只会使用库函数,基础不扎实,所以打基础的时候,千万不要迷恋库函数!!为了帮助兄弟们打下较好的基础,我整理了这篇字符串处理专题训练,内容从易到难,帮助你完美过度,夯实基础,其中更附带详细解析。建议兄弟们收藏,有空训练。
⭐️精彩回放⭐️
哈希专题 | 【哈希系列】舍友担心期末考睡不着,我连夜准备了这套哈希全套专题 |
几数之和系列 | 【几数之和系列】四数之和都不会做,面试官让我回去看力扣第一题 |
链表专题 | 【圣诞专场】刷完这套链表套题,面试官考链表的时候我笑出了声 |
⭐️知识导航⭐️
????1.进场须知
????2.处理字符,血战力扣
????1.反转字符串
????2.反转字符串||
????3.替换空格
????4.翻转字符串里的单词(中等)
????5.左旋转字符串
????6.最后一个单词的长度
????7.字符串中的第一个唯一字符
????3.基础字符串处理总结(题目总链接)
????1.进场须知
本章旨在为大家锻炼字符串处理的基础,在大家做题的同时,也该了解自己的语言是否含有相应的库函数,在进阶训练难题时,大家应该直接使用库函数,不然代码会非常冗余。因为进阶难题字符串的处理往往只是做题的一部分,而基础训练只需要对字符串处理。所以大家训练的时候一定也要记住是否有合适的库函数。
????2.处理字符,血战力扣
????1.反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
题目链接:反转字符串
题目分析:题目的要求非常简单,就是将字符数组反转,但要求在原数组上操作,那我们就不可以new一个新的char[]数组来交换。既然如此,那我们一定要想到利用双指针,一个指向数组头部,一个指向数组尾部,交换值后,同时向内移动继续,直到两个指针相遇。
方法:双指针
时间复杂度O(n):n为数组s的长度,一共进行了N/2次交换,常数1/2舍去,时间复杂度为O(n)
空间复杂度O(1):只用了两个int变量,复杂度为O(1)
class Solution {
public void reverseString(char[] s) {
//左指针指向头部,右指针指向尾部
int left=0;
int right=s.length-1;
while(left<right){
//交换值
exch(left,right,s);
//两指针同时向中间移动
left++;
right--;
}
}
//写一个交换方法
public void exch(int i,int j,char[] s){
char a=s[i];
s[i]=s[j];
s[j]=a;
}
}
????2.反转字符串||
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
题目链接:反转字符串||
题目解析:这道题是第一题的进阶,难点在于要知道合适的反转范围,我们用指针l和指针r来每次找到需要反转的范围。尤其是注意在最后,r是直接在l的基础上+k-1,有可能超出数组长度,所以每次需要在r和n-1之间取小值。这道题l和r的下标,建议画个图理解,不要光靠脑子想象,这是大忌。
方法:双指针
时间复杂度O(n):n为s的长度。
空间复杂度O(1)或O(n):这取决于你的语言,像Java中的String不可变,我们只能用一个char数组取修改,如果你的语言字符串可修改,可在原数组操作即可,这时时间复杂度就是O(1)。
class Solution {
public String reverseStr(String s, int k) {
char[] arr=s.toCharArray();
int n=arr.length;
//l指针每次+2k
for(int l=0;l<n;l=l+2*k){
//r每次在l的基础上+k-1(因为找的是下标,建议画个图写出下标理解一下)
int r=l+k-1;
//如果r超出了数组长度,这时r就比n-1大了,我们只取到n-1即可
reverse(arr,l,Math.min(n-1,r));
}
return String.valueOf(arr);
}
//写一个反转方法,反转char[]数组指定的索引区间
public void reverse(char[] arr,int i,int j){
while(i<j){
char a=arr[i];
arr[i++]=arr[j];
arr[j--]=a;
}
}
}
????3.替换空格
请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
题目链接:替换空格
题目分析:由于是字符串,有些语言的字符串是可修改的,某些语言的字符串是不可修改的,像Java的String是不可修改的。所以这里我们用StringBuilder,当然StringBuffer也可,两者都是可修改的字符串类,但是StringBuilder的效率更高。我们遍历题目给的String,如果不是空格,之间在StringBuilder后加上相同的,如果是空格,就加上%20,最后把StringBuilder转换为String返回即可。
方法: 利用可变字符串(这里也可用字符串数组),遍历相加
时间复杂度O(n):n为字符串的长度,主要是遍历的耗时
空间复杂度O(n):主要是创建了StringBuilder对象,长度最长可能为3n。
class Solution {
public String replaceSpace(String s) {
StringBuilder arr=new StringBuilder();
for(int i=0;i<s.length();i++){
//是空格就加上%20
if(s.charAt(i)==' '){
arr.append("%20");
}else{
//不是空格就之间加到arr末尾
arr.append(s.charAt(i));
}
}
//返回String形式
return arr.toString();
}
}
????4.翻转字符串里的单词(中等)
给你一个字符串 s ,逐个翻转字符串中的所有 单词 。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
请你返回一个翻转 s 中单词顺序并用单个空格相连的字符串。
说明:
输入字符串 s 可以在前面、后面或者单词间包含多余的空格。
翻转后单词间应当仅用一个空格分隔。
翻转后的字符串中不应包含额外的空格。
题目链接:翻转字符串里的单词
题目分析:这道题在字符串里算是较有难度的一题,因为它几乎考察了所有的字符串处理操作,想要效率高的完成,还是比较难的,即使是用API,很多人也无从下手,但是这也让我们了解了更多API的使用操作,是一道非常好的题。下面我为大家讲解多种方法以及它们的效率如何。
❗️考点解析:1.如何修剪掉两端空格
2.如何把单词反转过来
3.如何跳过中间连续的空格
方法1:双指针遍历倒插(重点掌握的方法)
首先我们把题目给的String转换为char[],还要有一个StringBuilder接收答案。通过从指针从头尾开始遍历,如果指的值为空格,均往中间移动,直到不为空格,这样就剪掉了首尾空格。因为要单词反转,所以从后面的right开始寻找单词,从right的位置,用一个index指针往左移动,找到为空格的位置停止,然后将index+1倒right的位置的单词遍历放入到StringBuilder。接着index继续移动找到不为空格的位置,然后把right更新过来,后面接着同样的操作知道完成数组的遍历得到答案。其实全程除了除去首尾空白,left都没动过,right也是靠index来更新,主要是index的移动。
由于思路光用语言难以表达,为大家找到一篇优质图解,建议认真观看一下——双指针图解
时间复杂度O(n):n为s的长度,只遍历了一次
空间复杂度O(n):主要用来存储字符串,因语言而定,字符串可变的语言空间复杂度可为O(1)
class Solution {
public String reverseWords(String s) {
//转换为char数组
char[] arr = s.toCharArray();
int left = 0;
int right = arr.length-1;
//用来接收答案
StringBuilder sb = new StringBuilder();
//去掉空格
while(arr[left]==' ') left++;
while(arr[right] == ' ') right--;
//注意循环结束条件
while(left <= right){
//index从right开始出发
int index = right;
//找到为空的地方停下来,这里要注意也得大于等于left,不然在走到最左边可能会数组越界
while(index >= left && arr[index] != ' ' ) index--;
//注意此时index是指向空格的,从inde+1到right一个个字符加入到StringBuilder中
for(int i = index+1 ; i <= right ; i++ ) sb.append( arr[i] );
//因为中间的单词之间需要加空格,所以index>left时,说明还在中间,我们补一个空格
if(index > left) sb.append(' ');
//然后我们继续移动index找到下一个单词的,这时也要保证index>=left,原理同上
while( index >= left && arr[index]==' ' ) index--;
//然后更新right到index
right = index;
}
return sb.toString();
}
}
方法2:API大法(虽然复杂度差不多,但其实效率比双指针低)
时间复杂度:O(n),其中 nn 为输入字符串的长度。
空间复杂度:O(n)O(n),用来存储字符串分割之后的结果
上面我们讲了这道题的考点解析,如果我们都能够用API来完成这些需求,那么代码就会很简单,前提是大家要会使用和了解这些API。
官方题解的API:
class Solution {
public String reverseWords(String s) {
// 除去开头和末尾的空白字符
s = s.trim();
// 正则匹配连续的空白字符作为分隔符分割
List<String> wordList = Arrays.asList(s.split("\\s+"));
//Collections带的reverse方法可以之间反转,但需要传入一个List
Collections.reverse(wordList);
//String.join方法很冷门,它可以在第二个参数的两两元素之间插入第一个参数
return String.join(" ", wordList);
}
}
我写的API法:
class Solution {
public String reverseWords(String s) {
//去掉首尾空格的API,需要会用
String c=s.trim();
//以连续的空格为切割符,这里涉及到正则表达式
//ps:split的使用很广泛,请一定要学会
String[] arr=c.split("\\s+");
StringBuilder a=new StringBuilder();
//从
for(int i=arr.length-1;i>=0;i--){
//从尾向头一个个单词插入
//每次插入后补一个空格
a.append(arr[i]+" ");
}
//最后一个单词后面不用空格,删掉最后一个
a.deleteCharAt(a.length()-1);
return a.toString();
}
}
????5.左旋转字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
题目链接:左旋转字符串
题目分析:题目的要求很简单,无非就是将前n个字符移到后面即可,我们可以用StringBuilder或者StringBuffer先接收后面的字符,再接收前n个字符。
方法1:StringBuffer接收
时间复杂度O(n):n为s的长度,遍历的耗时
空间复杂度O(n):主要是StringBuffer的消耗
PS:若面试要求只能用String,这里也可用String,只不过Python和Java中的String不可变,每次都要新建一个字符串且拼接的效率低,每次都要申请内存,当数据量大时效率低,且申请内存高。
class Solution {
public String reverseLeftWords(String s, int n) {
StringBuffer a=new StringBuffer();
for(int i=n;i<s.length();i++){
a.append(s.charAt(i));
}
for(int i=0;i<n;i++){
a.append(s.charAt(i));
}
return a.toString();
}
}
方法2:String的substring(效率高需掌握,常用API)
时间复杂度O(n):n为s的长度
空间复杂度O(n):n为s的长度
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n, s.length()) + s.substring(0, n);
}
}
????6.最后一个单词的长度
给你一个字符串 s
,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中最后一个单词的长度。
单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。
题目链接:最后一个单词的长度力扣最后一个单词的长度
分析:这道题可以看作为第四道题的子题,我们可以考虑同样的做法,反向遍历用指针去找到最后一个字母的起始索引和结尾索引,从而求得长度。
方法:反向遍历
时间复杂度O(n):n为字符串的长度,最多反向遍历整个字符串一遍
空间复杂度O(1):
class Solution {
public int lengthOfLastWord(String s) {
//从末尾开始遍历
int index=s.length()-1;
//结尾有空格有往左移动
while(s.charAt(index)==' '){
index--;
}
//此时index指向最后一个单词的最后一个字母,用a记录此时的位置
int a=index;
//index继续移动,保证index>0
//这里要注意index指向的是最后一个单词前面的空格
while(index>=0&&s.charAt(index)!=' '){
index--;
}
//相减获得长度
return a-index;
}
}
????7.字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
题目链接:字符串中的第一个唯一字符力扣字符串中的第一个唯一字符
题目分析:看到这个题目首先应该想到String的indexOf和lastindexOf两个API方法,其次应该想到暴力遍历的方法。效率高的方法,应该想到利用哈希,这里我们不采用map而采用数组来映射,因为数组也是哈希映射的一种体现。什么?你说你还不太会哈希或者不懂数组体现哈希?那你快看看这套哈希专题——【哈希系列】舍友担心期末考睡不着,我连夜准备了这套哈希全套专题
方法1:利用String的indexOf和lastIndexOf
时间复杂度O(n^2):涉及到API的源码,内部也是循环实现,这个方法复杂度较高
空间复杂度O(1)
class Solution {
public int firstUniqChar(String s) {
for(int i=0;i<s.length();i++){
//indexOf获得某个元素从头遍历第一次出现的位置
//lastIndexOf获得某个元素从尾到头遍历第一次出现的位置
//如果相等说明这个字母就一个
if(s.indexOf(s.charAt(i))==s.lastIndexOf(s.charAt(i))){
return i;
}
}
//没找到返回-1
return -1;
}
}
方法2: 利用数组哈希映射
时间复杂度O(n):进行两次遍历,这里也可用哈希表来做,只不过使用数组的效率更好,推荐大家两种都要尝试。
空间复杂度O(1):常数长度的数组消耗,看为O(1)的复杂度消耗
class Solution {
public int firstUniqChar(String s) {
//统计26个字母出现的频率
int[] arr=new int[26];
//遍历一次数组统计出现的每个字母出现的次数
for(int i=0;i<s.length();i++){
arr[s.charAt(i)-'a']++;
}
//再遍历一次,找到出现次数为1的字母
for(int i=0;i<s.length();i++){
if(arr[s.charAt(i)-'a']==1){
return i;
}
}
return -1;
}
}
????3.基础字符串处理总结(题目总链接)
其实我们发现几乎关于字符串的每道题,都或多或少可以利用API直接或者间接求的答案,由此可见了解字符串相关API的重要性。但我们在会使用的过程中,也应该掌握其原理,如果没有这个API你也能自己用实现这个功能,这才是我们的基本功能力。在进阶的难题中,字符串的处理只是一部分,它还会搭配其他的算法,为了简化代码这时我们就应该使用API来处理。所以大家在打好基础的同时,也一定要会使用相关的API。
1.反转字符串 | https://leetcode-cn.com/problems/reverse-string/ |
2.反转字符串ll | https://leetcode-cn.com/problems/reverse-string-ii/ |
3.替换空格 | https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/ |
4.反转字符串里的单词 | https://leetcode-cn.com/problems/reverse-words-in-a-string/ |
5.左选择字符串 | https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/ |
6.最后一个单词的长度 | https://leetcode-cn.com/problems/length-of-last-word/ |
7.字符串里第一个唯一字符 | https://leetcode-cn.com/problems/first-unique-character-in-a-string/ |
感觉有用的兄弟们,期望给个三连支持一下!!!感谢
文末小惊喜: