剑指 offer 第 3 天

2023-05-06,

第 3 天

字符串(简单)

剑指 Offer 05. 替换空格

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

示例 1:

输入:s = "We are happy."
输出:"We%20are%20happy."

限制:

0 <= s 的长度 <= 10000

题解思路:字符串拼接、数组反向填充

字符串拼接:利用 StringBuilder 可以方便的完成字符串的各种操作

class Solution {
public String replaceSpace(String s) {
StringBuilder res = new StringBuilder();
for (Character c : s.toCharArray()) {
if (c == ' ') {
res.append("%20");
}
else {
res.append(c);
}
}
return(res.toString());
} }

复杂度:时间 O(n) 空间 O(n)

String转为利用字符数组后,计算转换后一共所需空间,然后反向填充字符数组

class Solution {
public String replaceSpace(String s) {
char[] ch = s.toCharArray();
int count = 0;
// 统计空格数
for(int i = 0 ;i < ch.length;i++){
if(ch[i]==' ')
count++;
}
// 比原来的多2个字符,所以是原来的基础上+count*2
char[] ch2 = new char[ch.length+count*2]; int l = ch.length-1;
int r = ch2.length-1;
for(int i = 0;i<ch.length;i++){
if(ch[l]==' '){
ch2[r]='0';
ch2[r-1]='2';
ch2[r-2]='%';
l--;
r-=3;
}else{
ch2[r] = ch[l];
l--;
r--;
}
}
return new String(ch2);
}
}

复杂度:时间 O(n) 空间 O(n)

剑指 Offer 58 - II. 左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

示例 1:

输入: s = "abcdefg", k = 2
输出: "cdefgab"

示例 2:

输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"

限制:

1 <= k < s.length <= 10000

题解思路:字符串拼接、三次反转

字符串拼接:有不同实现方式,利用StringBuilder逐一append、调用subString()方法

subString() 方法:

class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n, s.length()) + s.substring(0, n);
}
}

StringBuilder 方法:

class Solution {
public String reverseLeftWords(String s, int n) {
StringBuilder res = new StringBuilder();
for(int i = n; i < n + s.length(); i++)
res.append(s.charAt(i % s.length()));
return res.toString();
}
}

复杂度:时间 O(n) 空间 O(n)

三次反转:利用三次字符串反转,首先反转前 k 个字符,然后反转后 n-k 个字符,最后整体反转 n 个字符,n为字符串长度

class Solution {
public String reverseLeftWords(String s, int n) {
StringBuffer s1 = new StringBuffer(s.substring(0, n)).reverse();
StringBuffer s2 = new StringBuffer(s.substring(n)).reverse();
return s1.append(s2).reverse().toString();
}
}

复杂度:时间 O(n) 空间 O(n)

字符串反转类似的题目很多,如反转单词、反转前缀、多次反转,利用好 String 内置方法可以方便解答

541. 反转字符串 II

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例 1:

输入:s = "abcdefg", k = 2
输出:"bacdfeg"

示例 2:

输入:s = "abcd", k = 2
输出:"bacd"

提示:

1 <= s.length <= 104
s 仅由小写英文组成
1 <= k <= 104

题解思路:过程模拟

过程模拟:一遍遍历遇到该反转的位置便反转,每次反转前判断 n 和 i + k 的大小,从而确定末尾剩余字符不足 k 时,反转多少个元素。反转操作使用双指针交换完成

class Solution {
public String reverseStr(String s, int k) {
int n = s.length();
char[] arr = s.toCharArray();
for (int i = 0; i < n; i += 2 * k) {
reverse(arr, i, Math.min(i + k, n) - 1);
}
return new String(arr);
} public void reverse(char[] arr, int left, int right) {
while (left < right) {
char temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
}
}

复杂度:时间 O(n) 空间 O(n)

151. 翻转字符串里的单词

给你一个字符串 s ,逐个翻转字符串中的所有 单词

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

请你返回一个翻转 s 中单词顺序并用单个空格相连的字符串。

说明:

输入字符串 s 可以在前面、后面或者单词间包含多余的空格。
翻转后单词间应当仅用一个空格分隔。
翻转后的字符串中不应包含额外的空格。

示例 1:

输入:s = "the sky is blue"
输出:"blue is sky the"

示例 2:

输入:s = "  hello world  "
输出:"world hello"
解释:输入字符串可以在前面或者后面包含多余的空格,但是翻转后的字符不能包括。

示例 3:

输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,将翻转后单词间的空格减少到只含一个。

示例 4:

输入:s = "  Bob    Loves  Alice   "
输出:"Alice Loves Bob"

示例 5:

输入:s = "Alice does not even like bob"
输出:"bob like even not does Alice"

提示:

1 <= s.length <= 104
s 包含英文大小写字母、数字和空格 ' '
s至少存在一个 单词

题解思路:利用好 API 快速解答、手动编写相关方法、双端队列、双指针

利用正则完成字符串切割:

// 妙啊
class Solution {
public String reverseWords(String s) {
// 除去开头和末尾的空白字符
s = s.trim();
// 正则匹配连续的空白字符作为分隔符分割
List<String> wordList = Arrays.asList(s.split("\\s+"));
// 列表反转
Collections.reverse(wordList);
// 字符串合并
return String.join(" ", wordList);
}
}

复杂度:时间 O(n) 空间 O(n)

手动反转字符串数组:

class Solution {
public String reverseWords(String s) {
String[] strs = s.trim().split(" "); // 删除首尾空格,分割字符串
StringBuilder res = new StringBuilder();
for(int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表
if(strs[i].equals("")) {
continue; // 遇到空单词则跳过
}
res.append(strs[i] + " "); // 将单词拼接至 StringBuilder
}
return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回
} public String reverseWords3(String s) {
StringBuilder sb = trimSpaces(s);
// 翻转字符串
reverse(sb, 0, sb.length() - 1);
// 翻转每个单词
reverseEachWord(sb);
return sb.toString(); }
public StringBuilder trimSpaces(String s) {
int left = 0, right = s.length() - 1;
// 去掉字符串开头的空白字符
while (left <= right && s.charAt(left) == ' ') {
++left;
}
// 去掉字符串末尾的空白字符
while (left <= right && s.charAt(right) == ' ') {
--right;
} // 将字符串间多余的空白字符去除
StringBuilder sb = new StringBuilder();
while (left <= right) {
char c = s.charAt(left); if (c != ' ') {
sb.append(c);
} else if (sb.charAt(sb.length() - 1) != ' ') {
sb.append(c);
}
++left;
}
return sb;
} public void reverse(StringBuilder sb, int left, int right) {
while (left < right) {
char tmp = sb.charAt(left);
sb.setCharAt(left++, sb.charAt(right));
sb.setCharAt(right--, tmp);
}
} public void reverseEachWord(StringBuilder sb) {
int n = sb.length();
int start = 0, end = 0;
while (start < n) {
// 循环至单词的末尾
while (end < n && sb.charAt(end) != ' ') {
++end;
}
// 翻转单词
reverse(sb, start, end - 1);
// 更新start,去找下一个单词
start = end + 1;
++end;
}
}
}

复杂度:时间 O(n) 空间 O(n)

双端队列:先字符串中一个一个单词处理,然后将单词压入队列的头部,再将队列转成字符串即可

class Solution {
public String reverseWords(String s) {
int left = 0, right = s.length() - 1;
// 去掉字符串开头的空白字符
while (left <= right && s.charAt(left) == ' ') {
++left;
}
// 去掉字符串末尾的空白字符
while (left <= right && s.charAt(right) == ' ') {
--right;
}
Deque<String> d = new ArrayDeque<String>();
StringBuilder word = new StringBuilder();
while (left <= right) {
char c = s.charAt(left);
if ((word.length() != 0) && (c == ' ')) {
// 将单词 push 到队列的头部
d.offerFirst(word.toString());
word.setLength(0);
} else if (c != ' ') {
word.append(c);
}
++left;
}
d.offerFirst(word.toString());
return String.join(" ", d);
}
}

复杂度:时间 O(n) 空间 O(n)

双指针:前后指针从后向前搜索单词位置,找到后加入 StringBuilder,继续搜索空格个数,循环如此,直到 left > right

class Solution {
public String reverseWords(String s) {
char[] c = s.toCharArray();
int left = 0;
int right = c.length-1;
StringBuilder str = new StringBuilder();
while(c[left]==' ') {
left++;
}
while(c[right] == ' ') {
right--;
}
while(left <= right){
int index = right;
while(index >= left && c[index] != ' ' ) {
index--;
}
for(int i = index+1 ; i <= right ; i++ ) {
str.append(c[i]);
}
if(index > left) {
str.append(' ');
}
while( index >= left && c[index]==' ' ) {
index--;
}
right = index;
}
return str.toString();
}
}

复杂度:时间 O(n) 空间 O(n)

2000. 反转单词前缀

难度简单2收藏分享切换为英文接收动态反馈

给你一个下标从 0 开始的字符串 word 和一个字符 ch 。找出 ch 第一次出现的下标 i反转 word 中从下标 0 开始、直到下标 i 结束(含下标 i )的那段字符。如果 word 中不存在字符 ch ,则无需进行任何操作。

例如,如果 word = "abcdefd"ch = "d" ,那么你应该 反转 从下标 0 开始、直到下标 3 结束(含下标 3 )。结果字符串将会是 "dcbaefd"

返回 结果字符串

示例 1:

输入:word = "abcdefd", ch = "d"
输出:"dcbaefd"
解释:"d" 第一次出现在下标 3 。
反转从下标 0 到下标 3(含下标 3)的这段字符,结果字符串是 "dcbaefd" 。

示例 2:

输入:word = "xyxzxe", ch = "z"
输出:"zxyxxe"
解释:"z" 第一次也是唯一一次出现是在下标 3 。
反转从下标 0 到下标 3(含下标 3)的这段字符,结果字符串是 "zxyxxe" 。

示例 3:

输入:word = "abcd", ch = "z"
输出:"abcd"
解释:"z" 不存在于 word 中。
无需执行反转操作,结果字符串是 "abcd" 。

提示:

1 <= word.length <= 250
word 由小写英文字母组成
ch 是一个小写英文字母

题解思路:反转部分字符串、

反转部分字符串:利用好 API 快速解答、手动编写方法

利用好 API 快速解答:

class Solution {
public String reversePrefix(String word, char ch) {
int left = 0,right = word.indexOf(ch);
if (right == -1) {
return word;
}
StringBuffer buffer = new StringBuffer(word.substring(left, right+1)).reverse();
buffer.append(word.substring(right+1));
return buffer.toString();
}
}

复杂度:时间 O(n) 空间 O(n)

手动编写方法:

class Solution {
// 找到下标d直接通过双指针反转就可以了
public String reversePrefix(String word, char ch) {
int left=0,right=0;
StringBuffer s = new StringBuffer(word);
for(;right<word.length();) {
if (word.charAt(right)==ch) {
break;
}
right++;
}
// 如果超出了说明没有匹配的字符
if (right>=word.length()) {
return word;
}else { while(left<right) {
// 通过while交换
char temp = s.charAt(right);
s.setCharAt(right,s.charAt(left));
s.setCharAt(left,temp);
left++;
right--;
}
}
return s.toString();
}
}

复杂度:时间 O(n) 空间 O(n)

剑指 offer 第 3 天的相关教程结束。

《剑指 offer 第 3 天.doc》

下载本文的Word格式文档,以方便收藏与打印。