在做字符串相关的题目时,要注意,不同的编程语言的不同特性:
- c++:STL库中定义了string类,string类底层实现类似线性表,元素是可以更改的
- python:string类是不可更改的,因此通常将string转成char类型的数组再进行处理
- JAVA:string类是不可更改的,通常使用
str.toCharArray()
函数将字符串str转成char
类型数组使用,或新定义一个StringBuilder
、StringBuffer
(线程安全的)类变量使用
反转字符串类型的题目中,不外乎包括如下三类:
题目介绍:
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
解题思想:
使用O(1)的额外空间:对于string可以更改的编程语言,利用双指针法即可实现。
定义左右两个指针,以相对方向移动,分别交换两个指针指向的元素,直到两个指针相遇。
时间复杂度O(n)。
交换两个元素:
Python实现:
class Solution(object):
def reverseString(self, s):
"""
:type s: List[str]
:rtype: None Do not return anything, modify s in-place instead.
"""
# 反转字符串,使用o(1)的空间复杂度
# 使用相对双指针法
left,right=0,len(s)-1
while left<right:
# temp=s[left]
# s[left]=s[right]
# s[right]=temp
s[left],s[right]=s[right],s[left]
left+=1
right-=1
题目介绍:
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
- 如果剩余字符少于 k 个,则将剩余字符全部反转。
- 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。
解题思想:
1、最简单的实现:
python借助已有函数,reversed()以及python的切片操作
值得注意点:
list--->string:str="".join(arr)
class Solution:
def reverseStr(self, s: str, k: int) -> str:
arr=list(s)
left=0
for left in range(0,len(s),2*k):
arr[left:left+k]=reversed(arr[left:left+k])
return "".join(arr)
class Solution {
public String reverseStr(String s, int k) {
StringBuilder res=new StringBuilder();
int i=0;
for(i=0;i<s.length();i+=2*k){
//首先对前k个进行翻转
for(int j=Math.min(i+k-1,s.length()-1);j>=i;j--){
res.append(s.charAt(j));
}
//然后直接将后k个进行复制
for(int j=i+k;j<Math.min(i+2*k,s.length());j++){
res.append(s.charAt(j));
}
}
return new String(res);
}
}
题目介绍:
给你一个字符串 s ,颠倒字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
**输入:**s = “the sky is blue” 输出:“blue is sky the”
解题思想1:
如果追求O(1)的空间复杂度:
C++实现:
1、删除字符串中的多余空格 ★
2、将字符串整体翻转
3、然后分别将每个单词进行翻转
class Solution {
public:
void delspace(string &s){
//采用双指针法删除多余的空格,类比“删除数组中的重复元素”的题目
int slow=0,fast=0;
//去掉字符串前面的空格
while(fast<s.size() && s[fast]==' '){
fast++;
}
//去掉字符串中间的多余空格
while(fast<s.size()){
if(fast-1>=0 && s[fast-1]==' ' && s[fast]==' '){
fast++;
}
else{
s[slow++]=s[fast++];
}
}
//去掉字符串末尾的空格:如果原字符串末尾有空格,则上述操作之后,在字符串末尾必然会保留一个空格,就是slow-1
if(slow-1>=0 && s[slow-1]==' '){
s.resize(slow-1);
}
else{
s.resize(slow);
}
}
void reversedString(string& s) {
//定义相对指针实现翻转
int left = 0, right = s.size() - 1;
while (left < right) {
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
void reversedWord(string& s) {
int left = 0, right = 0, pos = 0;
while (pos<s.size() && s[pos] != ' ') pos++;
right = pos - 1;
while (pos<=s.size() ){
while (left < right) {
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
left = pos + 1;
pos++;
while (pos<s.size() && s[pos] != ' ') pos++;
right = pos - 1;
}
}
string reverseWords(string s) {
// 追求O(1)的空间复杂度
// 自定义函数实现翻转:先去掉多余的空格;然后对字符串整个进行翻转,然后对每个单词进行翻转
delspace(s);
reversedString(s);
reversedWord(s);
return s;
}
};
JAVA实现:
空间复杂度O(n)
class Solution {
public String reverseWords(String s) {
//使用StringBuilder实现
s=s.trim();
StringBuilder sb=new StringBuilder(s);
//首先将整个sb进行翻转
reverse(sb,0,sb.length()-1);
//然后sb中的多余空格删除
int i=0;
while(i<sb.length()){
if(sb.charAt(i)==' '&& sb.charAt(i-1)==' '){
sb.deleteCharAt(i);
}else{
i++;
}
}
//然后将sb中的word分别翻转
int l=0;
for(int r=1;r<=sb.length();r++){
if(r==sb.length() || sb.charAt(r)==' '){
reverse(sb,l,r-1);
l=r+1;
}
}
return new String(sb);
}
public void reverse(StringBuilder sb,int begin,int end){
while(begin<end){
char c=sb.charAt(begin);
sb.setCharAt(begin,sb.charAt(end));
sb.setCharAt(end,c);
begin++;
end--;
}
}
}
题目介绍:
**字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。**请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
解题思想一:
直接两段字符串拼接返回即可。
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n)+s.substring(0,n);
}
}
解题思想二:(剑指Offer思想-151. 颠倒字符串中的单词一题的迁移)
与151.颠倒字符串中的单词非常相似。将前k个字符看成一个单词,后面的字符看成一个单词,即可类比151的解法。
采用 先整体翻转,然后局部翻转的方式。
C++实现:
class Solution {
public:
void reversed(string &s,int left,int right){
//将给定字符串的[left,right]范围进行翻转
while(left<right){
char temp=s[right];
s[right]=s[left];
s[left]=temp;
left++;
right--;
}
}
string reverseLeftWords(string s, int n) {
//坐旋转字符串,其实跟颠倒字符串中的单词是一种解法
//如果想使用O(1)的解法,那么类似“颠倒字符串解法”
//1、现将整个字符串翻转;
reversed(s,0,s.size()-1);
//2、然后将两个部分分别翻转
reversed(s,0,s.size()-n-1);
reversed(s,s.size()-n,s.size()-1);
return s;
}
};
其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
题目描述:
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
解题思想:
这道题如果要做到最优,那么可以考虑不使用额外空间。
具体实现如下:
注:
之前使用快慢双指针删除数组中的元素的时候,对slow进行赋值时都是一个位置对应赋值一个元素,
但是在该题中,可以看成是一个空格元素替换成“%20”三个元素,因此会存在一个位置赋值三个元素的情况,
如果还是从前向后处理,就会导致需要将后面的元素逐次向后移动的情况,非常消耗时间,
因此,进行改进,将slow指针指向扩充后的字符串末尾,这样就不会存在冲突了。
C++实现:
class Solution {
public:
string replaceSpace(string s) {
// 官方题解思路:
// 1、首先将字符串扩充至替换后的大小
// 2、然后从后向前,使用双指针对字符串进行替换修改
int count=0;
for(int i=0;s[i]!='\0';i++){
if(s[i]==' '){
count++;
}
}
int left=s.size()-1;
s.resize(s.size()+count*2);
int right=s.size()-1;
while(left<right){
if(s[left]!=' '){
s[right--]=s[left];
}
else{
s[right--]='0';
s[right--]='2';
s[right--]='%';
}
left--;
}
return s;
}
};
JAVA实现:
class Solution {
public String replaceSpace(String s) {
//JAVA中字符串是不可更改的,因此只能定义一个额外的变量存储结果,因此空间复杂度为O(n)
StringBuilder sb=new StringBuilder();
for(int i=0;i<s.length();i++){
if(s.charAt(i)!=' '){
sb.append(s.charAt(i));
}else{
sb.append("%20");
}
}
return new String(sb);
}
}
思想:采用快慢双指针算法删除字符串中的不需要的元素,
时间复杂度为:O(n)
在C++中,如果直接对字符串进行遍历–>调用erase函数删除,
时间复杂度为:O(n^2),因为erase函数本身的时间复杂度为O(n)
题目介绍:
输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
解题思想:
删除字符串中的多余空格 ★
void delspace(string &s){
//采用双指针法删除多余的空格,类比“删除数组中的重复元素”的题目
int slow=0,fast=0;
//去掉字符串前面的空格
while(fast<s.size() && s[fast]==' '){
fast++;
}
//去掉字符串中间的多余空格
while(fast<s.size()){
if(fast-1>=0 && s[fast-1]==' ' && s[fast]==' '){
fast++;
}
else{
s[slow++]=s[fast++];
}
}
//去掉字符串末尾的空格:如果原字符串末尾有空格,则上述操作之后,在字符串末尾必然会保留一个空格,就是slow-1
if(slow-1>=0 && s[slow-1]==' '){
s.resize(slow-1);
}
else{
s.resize(slow);
}
}
题目介绍:
定义一个函数,删除字符串中所有重复出现的字符。
例如,输入,“google”删除之后的结果是“gole”。
解题思想:
Hash table+双指针
对于字符串匹配(查找)问题,采用KMP算法可以避免重复匹配的问题。
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
KMP的精髓所在就是前缀表,
前缀表:起始位置到下标i之前(包括i)的子串中,有多大长度的相同前缀后缀。
详见,08 KMP算法 .
若两字符串互为旋转,则「其一字符串」必然为「另一字符串拓展两倍长度后(循环子串)」的子串。
题目描述:
字符串轮转。给定两个字符串s1和s2,请编写代码检查s2是否为s1旋转而成(比如,waterbottle是erbottlewat旋转后的字符串)。
** 输入**:s1 = “waterbottle”, s2 = “erbottlewat” ** 输出**:True
方法一:拼接查找子串
class Solution {
public:
bool isFlipedString(string s1, string s2) {
return s1.size()==s2.size() && (s1+s1).find(s2)!=string::npos;
}
class Solution {
public boolean isFlipedString(String s1, String s2) {
//如果s1是由s2轮转得到,则s2+s2构成的循环字符串中正好存在两个s1子串
if(s1.length()!=s2.length()) return false;
String s=s2+s2;
return s.indexOf(s1)!=-1;
}
}