字符串基础练习¶
约 1730 个字 634 行代码 1 张图片 预计阅读时间 14 分钟
本篇介绍¶
因为字符串在C++中是字符数组,所以基本操作与数组是大致相似的,其相关操作的时间复杂度也是基本一致的,所以本篇主要介绍字符串相关的题目和部分字符串的算法,并不会涉及到字符串的统一解法
力扣125.验证回文串¶
问题描述:
Quote
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个回文串。
字母和数字都属于字母数字字符。
给你一个字符串s
,如果它是回文串,返回true
;否则,返回false
。
示例 1:
C++ | |
---|---|
1 2 3 |
|
示例 2:
C++ | |
---|---|
1 2 3 |
|
示例 3:
C++ | |
---|---|
1 2 3 4 |
|
思路分析:
-
解法1:逆序比较
这个思路比较直观,因为题目提到字符串还包含符号,所以需要创建一个新的字符串,将字符和数字拼接到该字符串中,再逆序该字符串,将逆序结果与该字符串比较,如果相等返回
true
,否则返回false
-
解法2:双指针
双指针的思路就是通过比较两个指针当前的字符是否相等作为判断条件,如果相等,就让两个指针同时向中间移动,注意需要包括二者相遇的情况,如果不相等直接返回
false
。如果其中一个指针指向的是符号,那么一直让其移动直到找到字母或者数字即可。上面的一系列步骤都涉及到移动指针,所以在获取指针指向的位置的字符时一定要确保没有越界访问的问题
参考代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
力扣344.反转字符串¶
问题描述:
Quote
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组s
的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用O(1)的额外空间解决这一问题。
示例 1:
C++ | |
---|---|
1 2 |
|
示例 2:
C++ | |
---|---|
1 2 |
|
思路分析:
反转字符串的本质就是依次交换对应位置的字符,例如"H"和"h"交换、"a"和"a"交换,以此类推。根据思路就可以利用到双指针left
和right
分别指向首尾,二者向中间遍历,如果同时指向一个字符,则可以考虑不进行交换,所以交换结束条件就是left<right
参考代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 |
|
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
力扣541.反转字符串II¶
问题描述:
Quote
给定一个字符串s
和一个整数k
,从字符串开头算起,每计数至2k
个字符,就反转这2k
字符中的前k
个字符。
- 如果剩余字符少于
k
个,则将剩余字符全部反转。 - 如果剩余字符小于
2k
但大于或等于k
个,则反转前k
个字符,其余字符保持原样。
示例 1:
C++ | |
---|---|
1 2 |
|
示例 2:
C++ | |
---|---|
1 2 |
|
思路分析:
本题首先需要理解题目的要求,题目看似给出了三个要求,实际上就只有两个要求:
- 字符数量小于
k
,则反转所有字符 - 字符数量大于等于
k
但小于2k
,则反转前k
个
根据这两个要求就可以模拟出本题需要的结果
关键步骤:
在遍历字符串时,其实每一次只需要考虑2k
部分即可,当2k
部分全部处理完成后,下标可以直接跳到2k+1
的位置,而没必要一个字符一个字符向后移动
参考代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
卡码网KamaCoder.替换数字¶
问题描述:
Quote
题目描述
给定一个字符串s
,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number
。例如,对于输入字符串a1b2c3
,函数应该将其转换为anumberbnumbercnumber
。
输入描述
输入一个字符串s
,s
仅包含小写字母和数字字符。
输出描述
打印一个新的字符串,其中每个数字字符都被替换为了number
输入示例
C++ | |
---|---|
1 |
|
输出示例
C++ | |
---|---|
1 |
|
提示信息
数据范围:
C++ | |
---|---|
1 |
|
思路分析:
-
解法1:异地处理
异地处理的方式很简单,遍历到数字字符就插入一个
number
,否则插入原字符,最后返回新字符数组 -
解法2:原地处理
原地处理的方式就是预先开辟比原字符串大的空间,具体开多大可以根据题目要求计算,因为题目需要遇到一个数字字符就进行替换,并且替换为
number
,所以需要额外开辟数字字符数量*number字符个数-1
的空间。在遍历原字符串的过程中,如果遇到了数字字符,就需要移动其后面所有的字符为插入number
腾出位子
参考代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
力扣151. 反转字符串中的单词¶
问题描述:
Quote
给你一个字符串s
,请你反转字符串中单词的顺序。
单词是由非空格字符组成的字符串。s
中使用至少一个空格将字符串中的单词分隔开。
返回单词顺序颠倒且单词之间用单个空格连接的结果字符串。
注意:输入字符串s
中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
示例 1:
C++ | |
---|---|
1 2 |
|
示例 2:
C++ | |
---|---|
1 2 3 |
|
示例 3:
C++ | |
---|---|
1 2 3 |
|
思路分析:
-
解法1:暴力解法
本题的暴力解法很简单,因为反转后的字符串不能包含多余的空格以及字符串前后的空格,所以需要两个函数分别用于去除首尾空格和中间多余空格。接着就是模拟过程,可以考虑头插单词
-
解法2:整体反转+单词单个反转
先整体反转再对某一个部分进行反转就可以让指定部分是逆向,其他部分是正向的,这在旋转字符串中也是常见的思路。在本题中依旧先要处理首尾空格和中间多余空格问题,可以和暴力解法一样,通过两个函数分别处理首尾空格和中间多余空格,但是为了讲本题的空间复杂度讲到O(1),考虑使用移除元素的思路来处理首尾空格和中间多余空格,因为本质移除字符串中的空格就是移除字符数组中内容为空格的元素。处理完空格后就可以通过整体反转,再针对每一个单词单独反转即可
参考代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
|
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
|
卡码网KamaCoder.右旋字符串¶
问题描述:
Quote
题目描述
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串s
和一个正整数k
,请编写一个函数,将字符串中的后面k
个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串abcdefg
和整数2,函数应该将其转换为fgabcde
。
输入描述
输入共包含两行,第一行为一个正整数k
,代表右旋转的位数。第二行为字符串s
,代表需要旋转的字符串。
输出描述
输出共一行,为进行了右旋转操作后的字符串。
输入示例
C++ | |
---|---|
1 2 |
|
输出示例
C++ | |
---|---|
1 |
|
提示信息
数据范围:
C++ | |
---|---|
1 2 |
|
思路分析:
右旋字符串就是最典型的整体反转再局部反转的题目,假设字符串有n
个字符,可以考虑先整体反转,再反转前k
个字符,已经后n - k
个字符
参考代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
力扣28.找出字符串中第一个匹配项的下标¶
问题描述:
Quote
给你两个字符串haystack
和needle
,请你在haystack
字符串中找出needle
字符串的第一个匹配项的下标(下标从0开始)。如果needle
不是haystack
的一部分,则返回-1。
示例 1:
C++ | |
---|---|
1 2 3 4 |
|
示例 2:
C++ | |
---|---|
1 2 3 |
|
思路分析:
-
解法1:暴力解法
暴力解法就是使用两层
for
循环,外层遍历原字符串,内层遍历子字符串,遇到不匹配的字符时,外层循环更新到当前字符的下一个字符继续匹配直到遇到完全匹配或者没有匹配返回-1 -
解法2:KMP算法
KMP算法就是优化字符串匹配问题的暴力解法,主要思路是利用已知条件减少重复匹配,具体见KMP算法部分
参考代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
力扣459.重复的子字符串¶
问题描述:
Quote
给定一个非空的字符串s
,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
C++ | |
---|---|
1 2 3 |
|
示例 2:
C++ | |
---|---|
1 2 |
|
示例 3:
C++ | |
---|---|
1 2 3 |
|
思路分析:
-
解法1:暴力解法
暴力解法就是枚举子字符串依次匹配,如果匹配成功就说明由该子字符串重复多次构成,否则不是。在暴力解法中,可以先判断枚举的子字符串的长度是否可以被原字符串的长度整除,如果可以则说明可能是该字符串,接着从该位置开始枚举字符即可
暴力解法里面有一个优化细节,因为子串至少需要重复一次,所以子串的长度一定不会超过原字符串长度的一半
-
解法2:双倍字符串
对于由某一个子串多次重复形成的字符串来说,如果将该字符串再重复一遍一定能在新字符串中找到原字符串,需要注意,拼接后的字符串需要去掉首尾字符,防止出现在第一组字符串和最后一组字符串中查找到原字符串
-
解法3:KMP算法
查找字符串就是KMP算法要做到的事情,但是本题并没有直接告诉模式串,所以首先需要获取到模式串,如何找到这个模式串也可以使用KMP算法,主要就是利用其
next
数组,以文本串ababab
为例,观察下图:可以看到如果一个字符串是由某一个子串重复多次构成的,那么最长相等前后缀不包含的子字符串一定是构成原字符串的基础子串
最后,获取到构成原字符串的子串就可以通过取余运算判断余数是否为0判断是否是由子串重复多次构成的字符串,但是注意判断
next
数组最后一个元素不为0,因为如果其为0,说明根本不存在最长相等前后缀,也就就说明与原字符串并不是由某一个子串重复多次构成Note
关于KMP算法部分具体看KMP算法,本题不详细介绍其原理
参考代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
力扣8.字符串转换整数(atoi)¶
Quote
请你来实现一个 myAtoi(string s)
函数,使其能将字符串转换成一个32位有符号整数。
函数 myAtoi(string s)
的算法如下:
- 空格:读入字符串并丢弃无用的前导空格(
" "
) - 符号:检查下一个字符(假设还未到字符末尾)为
-
还是+
。如果两者都不存在,则假定结果为正。 - 转换:通过跳过前置零来读取该整数,直到遇到非数字字符或到达字符串的结尾。如果没有读取数字,则结果为0。
- 舍入:如果整数数超过 32 位有符号整数范围\([−2^{31}, 2^{31} − 1]\),需要截断这个整数,使其保持在这个范围内。具体来说,小于\(−2^{31}\)的整数应该被舍入为\(−2^{31}\),大于\(2^{31} − 1\)的整数应该被舍入为\(2^{31} − 1\)。
返回整数作为最终结果。
示例 1:
C++ | |
---|---|
1 2 3 4 5 6 7 |
|
示例 2:
C++ | |
---|---|
1 2 3 4 5 6 |
|
示例 3:
C++ | |
---|---|
1 2 3 4 5 6 |
|
示例 4:
C++ | |
---|---|
1 2 3 4 5 6 |
|
示例 5:
C++ | |
---|---|
1 2 3 4 |
|
思路分析:
- 开始遇到非数字字符时返回0
-
否则继续
- 跳过空格
" "
-
遇到
+
或-
时处理返回值正负Note
注意
+
也要处理,+1
和1
虽然返回结果相同,但是如果直接输入的是+1
则此时下标指向+
而非数字1,从而导致最后的结果错误 -
处理数值返回
Note
注意处理数值时需要考虑每一次的计算结果是否超过32位整型的最大值和最小值
- 跳过空格
细节处理:
- 在获取字符串的数值时,有两种方法,第一种方法是将每一次获取的数值存入一个
vector<int>
中,最后根据位权求和,例如42
可以表示为4 * (int)pow(10, vector<int>().size() - 1 - 0) + 2 * (int)pow(10, vector<int>().size() - 1 - 1)
,但是这种方法如果存在前导0,那么也会放入vector<int>
中,从而导致需要额外处理前导0,所以需要考虑使用第二种方法:考虑到每一次都是从高位开始读取,则可以使用ret = ret * 10 + digit
(其中ret
为返回值,初始化为0,digit
为每一次获取的数字),在遍历字符数组时,例如542,高位优先读取,所以先读到5,此时,digit
为5,ret * 10
为0,所以此时ret
为5,接着遇到字符4,此时digit
为4,ret*10
为50,故ret
结果为54,最后遇到2,digit
为2,ret*10
为540,故结果为542。这个方法适合正向遍历,反向遍历还是需要用到vector
,那么如果存在前导0,这个方法则不需要考虑,因为当前导字符为0时,digit
为0,ret
也为0,ret*10+digit
就会为0,即ret
为0,如此往复直到遇到非前导0 - 越界问题:字符不存在越界,但是
int
类型存在,在32位系统下,范围为[INT_MIN, INT_MAX]
,因为最后的结果存储在ret
中,所以每次计算前判断ret
是否大于INT_MAX
或者小于INT_MIN
,因为越界只会存在于ret = ret*10+digit
中,如果结果ret
此时越界了,那么计算*10+digit
前可能没有越界,所以考虑计算式(INT_MAX - digit) / 10
的结果为临界值,如果ret
大于该式,那么ret = ret*10+digit
肯定越界
参考代码:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|