前言: \textcolor{Green}{前言:} 前言:
一天一个知识点
字符串更多的在做题中使用的方法。
尤其是String
和char
这两个应该怎么转换,以及字符串自带的方法。
题目来源: \textcolor{blue}{题目来源:} 题目来源:反转字符串Ⅱ
等级:简单 \textcolor{OrangeRed}{等级:简单} 等级:简单
题目描述
给定一个字符串 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
代码编写
方法1
class Solution {
public String reverseStr(String s, int k) {
// string 转换成char数组
char[] c = s.toCharArray();
for (int i = 0; i < c.length; i += 2 * k) {
int left = i;
int right = Math.min(c.length - 1, left + k - 1);
while (left < right) {
c[left] ^= c[right];
c[right] ^= c[left];
c[left] ^= c[right];
right--;
left++;
}
}
// char数组转换成String,可以直接转
return new String(c);
}
}
注意点
在遍历字符串的过程中,只要让 i += (2 * k)
,i
每次移动 2 * k
就可以,之后判断是否需要有反转的区间。
因为要找的也就是每2 * k 区间的起点。
可以提炼的知识
char[]
和 String
之间进行转换String s = "hxl";
char[] chars = s.toCharArray();
string sb = new String(chars);
int tmp = a;
a = b;
b = a;
a = a ^ b;
b = a ^ b;
a = a ^ b;
题目来源: \textcolor{blue}{题目来源:} 题目来源:替换空格
等级:简单 \textcolor{OrangeRed}{等级:简单} 等级:简单
题目描述
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
限制:
0 <= s 的长度 <= 10000
代码编写
方法1:直接进行替换
class Solution {
public String replaceSpace(String s) {
if (s == null) {
return s;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == ' ') {
sb.append("%20");
} else {
sb.append(s.charAt(i));
}
}
return sb.toString();
}
}
方法2:双指针
String
是不可变的,我们不能直接对它的长度进行改变,需要转换成StringBuilder
class Solution {
public String replaceSpace(String s) {
if (s == null || s.length() == 0) {
return s;
}
int len = s.length();
// 扩充长度
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
if (s.charAt(i) == ' ') {
sb.append(" ");
}
}
// 没有空格直接返回
if (sb.length() == 0) {
return s;
}
// 从后往前进行转换
int left = len - 1;
// 一个空格变成三个空间
s += sb.toString();
// s变了,长度也就变了,这个扩展的最后一个字符
int right = s.length() - 1;
char[] ch = s.toCharArray();
while (left >= 0) {
if (ch[left] == ' ') {
ch[right--] = '0';
ch[right--] = '2';
ch[right] = '%';
} else {
ch[right] = ch[left];
}
right--;
left--;
}
return new String(ch);
}
}
可以提炼的知识
O(n^2)
,因为每次添加元素都要将添加元素之后的所有元素向后移动。 题目来源: \textcolor{blue}{题目来源:} 题目来源:反转字符串中的单词
等级:中等 \textcolor{OrangeRed}{等级:中等} 等级:中等
题目描述
给你一个字符串 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"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。
提示:
1 <= s.length <= 104
s 包含英文大小写字母、数字和空格 ' '
s 中 至少存在一个 单词
代码编写
方法1
class Solution {
public String reverseWords(String s) {
int left = 0, right = s.length() - 1;
Deque<String> deq = new ArrayDeque<>();
StringBuilder word = new StringBuilder();
while (left <= right && s.charAt(right) == ' ') {
--right;
}
while (left <= right) {
char c = s.charAt(left);
if (word.length() != 0 && c == ' ') {
deq.offerFirst(word.toString());
word.setLength(0);
} else if (c != ' ') {
word.append(c);
}
++left;
}
deq.offerFirst(word.toString());
return String.join(" ", deq);
}
}
注意点
我这里只去除了尾部的空格,没有去除首部。经测试,首部的空格可以通过双端队列将单词push到队列的头部,这个时候进行判断去除首部空格。
StringBuilder
可以直接设置长。
可以提炼的知识
offerFirst()
方法在java.lang包中可用。offerFirst()
方法用于将给定元素添加到此双端队列的前面。offerFirst()
方法是一种非静态方法,只能通过类对象访问,如果尝试使用类名称访问该方法,则会收到错误消息。offerFirst()
方法在双端队列中添加元素时可能会引发异常。NullPointerException
:当给定元素为null时,可能引发此异常。例如题目中的 S t r i n g . j o i n ( " " , d e q ) String.join(" ", deq) String.join("",deq) ,是将 deq 中的每个元素通过空格连接。
题目来源: \textcolor{blue}{题目来源:} 题目来源:剑指 Offer 58 - II. 左旋转字符串
等级:简单 \textcolor{OrangeRed}{等级:简单} 等级:简单
题目描述
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = "abcdefg", k = 2
输出: "cdefgab"
示例 2:
输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"
限制:
1 <= k < s.length <= 10000
代码编写
方法1
先对前面n个反转,再反转后面的,最后整体进行反转。
class Solution {
public String reverseLeftWords(String s, int n) {
if (n > s.length()) {
return s;
}
int len = s.length();
StringBuilder sb = new StringBuilder(s);
reverseString(sb, 0, n - 1);
reverseString(sb, n, len - 1);
return sb.reverse().toString();
}
public void reverseString(StringBuilder sb, int start, int end) {
while (start < end) {
char temp = sb.charAt(start);
sb.setCharAt(start, sb.charAt(end));
sb.setCharAt(end, temp);
start++;
end--;
}
}
}
方法2
使用原始数组进行反转。
用上面的思路:先反转前n个,再反转剩下的,最后反转整体。
新的思路:先整体反转,再反转前面的,最后反转后面的n个
class Solution {
public String reverseLeftWords(String s, int n) {
if (n > s.length()) {
return s;
}
char[] ch = s.toCharArray();
int len = s.length();
reverse(ch, 0, len - 1);
reverse(ch, 0, len - n - 1);
reverse(ch, len - n, len - 1);
return new String(ch);
}
public void reverse(char[] ch, int left, int right) {
while (left < right) {
ch[left] ^= ch[right];
ch[right] ^= ch[left];
ch[left] ^= ch[right];
left++;
right--;
}
}
}
可以提炼的知识
sb.reverse().toString();
下面两个涉及到 K M P 算法,后续会讲解 \textcolor{blue}{下面两个涉及到KMP算法,后续会讲解} 下面两个涉及到KMP算法,后续会讲解
28. 找出字符串中第一个匹配项的下标
459. 重复的子字符串
双指针
使用双指针法实现反转字符串的操作,双指针法在数组,链表和字符串中很常用。
很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。可以参考题目二。
反转
三和四都是反转,那么具体是先整体反转还是先局部反转,可以根据自己思路来。
用相同的规律进行字符串处理时,可以通过for循环的条件来降低复杂度。
复习一下StringBuilder
、String
、StringBuffer
String
不可修改。StringBuffer
与StringBuilder
的内容可以修改,继承自AbstractStringBuilder类,实现原理都基于可修改的char数组StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。String是常量string
;StringBuilder
;StringBuffer
String
类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String
对象。StringBuffer
每次都会对 StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder
相比使用 StringBuffer
仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。拼接字符串
字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder
调用 append()
方法实现的,拼接完成之后调用 toString()
得到一个 String
对象 。