344.反转字符串
很简单,就是双指针法,问题就是,卡哥题解里的,不用中间变量就能交换两个数字的方法:使用异或运算符解决两数交换问题。
代码:
public void reverseString(char[] s) {
// 双指针法
int left = 0;
int right = s.length - 1;
while(left < right){ // 无论奇数偶数,最中间的字符不用交换
s[left] ^= s[right]; // 此时s[l] = s[l]^s[r].........1
s[right] ^= s[left]; // 此时s[r] = 1式^s[r] = s[r]^s[l]^s[r] = s[l]........2
s[left] ^= s[right]; // 此时s[l] = s[l]^s[r] = 1式^2式 = s[l]^s[r]^s[l]
// 整体移动指针
left++;
right--;
}
}
不使用中间变量来交换两数据(int型、char型、float型、double型)
使用异或运算符来操作,利用异或的运算性质:
- 任意一个变量a与其自身进行异或运算,结果为0,即a^a=0
- 任意一个变量a与0进行异或运算,结果不变,即a^0=X
- 异或运算具有可结合性,即a^b^c=(a^b)^c=a^(b^c)
- 异或运算具有可交换性,即a^b=b^a
所以,上述代码中交换就很好理解了:
s[ l ] ^= s[ r ]; // 此时s[l] = s[l]^s[r].........1
s[ r ] ^= s[ l ]; // 此时s[r] = 1式^s[r] = s[r]^s[l]^s[r] = s[l]........2
s[ l ] ^= s[ r ]; // 此时s[l] = s[l]^s[r] = 1式^2式 = s[l]^s[r]^s[l]
541. 反转字符串II
这题关键在于首指针的移动,根据首指针,确定需要反转的尾指针(首指针 + k),如果尾指针大于字符串长度了,那就让尾指针等于length - 1即可。这题还有一个关键,可以巧妙的利用for循环的特性,借用代码随想录的一句话:
所以当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。
即for循环里的i不是自增1,可以改成自增2k,满足每2k段处理k个段字符,每2k段处理k段字符。
代码:
public String reverseStr(String s, int k) {
char[] ch = s.toCharArray();
// 题目的意思其实概括为 每隔2k个反转前k个,尾数不够k个时候全部反转,所以起始位置的选取是个关键,可以直接利用for循环的结构来设计,就是改变一下i的增量就行
for(int i = 0; i < ch.length; i += 2 * k){
int start = i;
// 这里是判断尾数够不够k个来取决end指针的位置
int end = Math.min(ch.length - 1, start + k - 1);// 用while循环实现反转,反转包括结尾这个字符,所以要减1
while(start < end){// 用异或运算反转
ch[start] ^= ch[end];
ch[end] ^= ch[start];
ch[start] ^= ch[end];
start++;
end--;
}
}
return new String(ch);
}
new String(ch):基于给定的字符数组 ch
创建一个新的 String
对象
速度要快于用使用String类的方法(String.valueOf(char[]))来将字符数组转化为String类型。
卡码网:54.替换数字
我用的不是双指针思路,直接用“在线”的思想,不用开辟新空间,直接遇到数字在后面输出number即可,时间复杂度就是O(n),遍历一遍数组即可。
代码:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
String str = "";
Scanner sc = new Scanner(System.in);
str = sc.next();
for(int i = 0;i < str.length(); i++) {
if(str.charAt(i) >= '0' && str.charAt(i) <= '9') {
System.out.print("number");
}else {
System.out.print(str.charAt(i));
}
}
}
}
但是一开始有想用StringBuffer做(后面想到了这个“在线”的做法,就不用了),但是对StringBuffer的常用方法不是很熟悉,下面总结一下:
StringBuffer常用方法
1. append(T t)
: 将参数t的字符串表示附加到此序列的末尾。
- T可以是各种数据类型。
2. insert(int offset, T t)
:在指定位置插入参数t的字符串表示。
3. deleteCharAt(int index):
删除指定位置的字符。
4. setCharAt(int index, char ch):
替换指定位置的字符。
5. CharAt(int index):
返回指定位置的字符。
6. reverse()
:反转此序列中的字符。
7. trimToSize():
将此序列的中存储空间缩小到和字符串长度一样的长度,减少空间的浪费。
那String方法也总结一下吧:
String常用方法
1. String(char[] value)
: 根据字符数组创建一个字符串。
2. substring(int beginIndex, int endIndex)
: 返回从beginIndex
到endIndex
(不包括)之间的子字符串。
3. replace(char oldChar, char newChar)
: 将此字符串中的所有指定字符替换为另一字符。
4. split(String str):根据str将字符串分割(返回的是一个字符串数组)。
151.翻转字符串里的单词
看了卡哥视频做的,可能是小脑萎缩了,脑子里想的是双指针法删除,结果做的时候还是用额外开辟空间的StringBuilder。。。这次是直接上卡哥代码吧,敲累了。
代码:
//用 char[] 来实现 String 的 removeExtraSpaces,reverse 操作
public String reverseWords(String s) {
char[] chars = s.toCharArray();
//1.去除首尾以及中间多余空格
chars = removeExtraSpaces(chars);
//2.整个字符串反转
reverse(chars, 0, chars.length - 1);
//3.单词反转
reverseEachWord(chars);
return new String(chars);
}
//1.用 快慢指针 去除首尾以及中间多余空格,可参考数组元素移除的题解
public char[] removeExtraSpaces(char[] chars) {
int slow = 0;
for (int fast = 0; fast < chars.length; fast++) {
//先用 fast 移除所有空格
if (chars[fast] != ' ') {
//在用 slow 加空格。 除第一个单词外,单词末尾要加空格
if (slow != 0)
chars[slow++] = ' ';
//fast 遇到空格或遍历到字符串末尾,就证明遍历完一个单词了
while (fast < chars.length && chars[fast] != ' ')
chars[slow++] = chars[fast++];
}
}
//相当于 c++ 里的 resize()
char[] newChars = new char[slow];
System.arraycopy(chars, 0, newChars, 0, slow);
return newChars;
}
//双指针实现指定范围内字符串反转,可参考字符串反转题解
public void reverse(char[] chars, int left, int right) {
if (right >= chars.length) {
System.out.println("set a wrong right");
return;
}
while (left < right) {
chars[left] ^= chars[right];
chars[right] ^= chars[left];
chars[left] ^= chars[right];
left++;
right--;
}
}
//3.单词反转
public void reverseEachWord(char[] chars) {
int start = 0;
//end <= s.length() 这里的 = ,是为了让 end 永远指向单词末尾后一个位置,这样 reverse 的实参更好设置
for (int end = 0; end <= chars.length; end++) {
// end 每次到单词末尾后的空格或串尾,开始反转单词
if (end == chars.length || chars[end] == ' ') {
reverse(chars, start, end - 1);
start = end + 1;
}
}
}
总结:
虽然不是自己敲的(自己敲的能过,但是太拉了,还敲了很久。。),不过还是有收获的。
双指针法删除:
快指针,是负责移动遍历原来的数组。慢指针,是负责定位需要保存的位置的。当快指针移动到不需要删除的位置时(满足条件的位置),就令慢指针所指数组位置等于快指针所指位置数据(这样,保证了不需要的数据被覆盖,唯一需要注意的是,数组大小需要压缩)。
双指针法删除空格:
先看代码:
for (int fast = 0; fast < chars.length; fast++) {
//先用 fast 移除所有空格
if (chars[fast] != ' ') {
//在用 slow 加空格。 除第一个单词外,单词末尾要加空格
if (slow != 0)
chars[slow++] = ' ';
//fast 遇到空格或遍历到字符串末尾,就证明遍历完一个单词了
while (fast < chars.length && chars[fast] != ' ')
chars[slow++] = chars[fast++];
}
}
这段代码的巧妙之处在于,for循环不断移动fast指针,而slow指针能及时更新单词末尾的空格,原因在于:每次遇到新单词就会进入if语句中,当第一次进入if语句时,显然,此时slow=0,就不加入空格,而while循环处理的是一个单词的遍历,当遇到字符串末尾或者遇到空格表示遍历完了,此时slow在这个单词的末尾,下次只有再进入这个if,直接让chars[slow++] = ' ';便能在单词末尾加上空格了。
压缩数组:
利用System.arraycopy方法,将一个数组中的元素复制到另一个数组中。如:
System.arraycopy(chars, 0, newChars, 0, slow);
chars
是源数组,从该数组中复制元素。0
是源数组的起始位置,表示从该数组的第一个元素开始复制。newChars
是目标数组,将复制的元素放到这个数组中。0
是目标数组的起始位置,表示从该数组的第一个元素开始放置复制的元素。slow
表示要复制的元素个数。
简单地说,这行代码从 chars
数组的开始位置复制 slow
个元素到 newChars
数组的开始位置。从而达到“压缩”的目的(因为slow是最终满足条件的数组大小)。
代码的复用性:
本题思路中,把多余空格删除之后,先反转整个字符串,再反转每个单词,而无论是反转整个字符串还是每个单词,核心代码都是“从字符串的起始位置start,反转到字符串的终止位置end”
所以反转整个字符串这样定义:
public void reverse(char[] chars, int left, int right)
这样,在反转单词的时候,根据空格或者结尾,确定一个单词的left和right,直接调用即可。
卡码网:55.右旋转字符串
自己用了一个取巧的思路,即:把需要右旋的提前输出即可,我称之为“提前输出法”。与卡哥的“整体反转+局部反转”法不一样,我认为更好理解。
1. 提前输出法:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int k = sc.nextInt();
char[] charS = sc.next().toCharArray();
for(int i = 0; i < charS.length; i++) {
if(i >= charS.length - k) {// 如果是需要右旋的字符,提前输出即可
System.out.print(charS[i]);
}
}
for(int i = 0; i < charS.length - k; i++) {
System.out.print(charS[i]);// 把剩下的字符输出
}
}
}
2. 整体反转+局部反转法:
和151.翻转字符串里的单词思路类似,只需要实现一个reverse即可。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int k = sc.nextInt();
char[] charS = sc.next().toCharArray();
reverse(charS,0,charS.length - 1);
reverse(charS,0,k - 1);
reverse(charS,k,charS.length - 1);
System.out.print(charS);
}
private static void reverse(char[] charS, int start, int end) {
while(start < end) {
charS[start] ^= charS[end];
charS[end] ^= charS[start];
charS[start] ^= charS[end];
start++;
end--;
}
}
}