关于库函数的使用:
如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数。
如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。
题目1:344.反转字符串
方法一:用temp
//用temp来交换数值
class Solution {
public void reverseString(char[] s) {
int l = 0;
int r = s.length - 1;
while(l < r){
char temp = s[l];
s[l] = s[r];
s[r] = temp;
l++;
r--;
}
}
}
方法二:用异或性质
class Solution {
public void reverseString(char[] s) {
int l = 0;
int r = s.length - 1;
while (l < r) {
s[l] ^= s[r]; //构造 a ^ b 的结果,并放在 a 中
s[r] ^= s[l]; //将 a ^ b 这一结果再 ^ b ,存入b中,此时 b = a, a = a ^ b
s[l] ^= s[r]; //a ^ b 的结果再 ^ a ,存入 a 中,此时 b = a, a = b 完成交换
l++;
r--;
}
}
}
使用异或交换两个数:
通过异或的性质可以实现两个变量交换值而不需要使用临时变量。
假设有两个变量 a 和 b,希望交换它们的值,可以按照以下步骤进行:
步骤 1: a = a ^ b
将 a 和 b 进行异或运算,结果存回 a 中。此时,a 存储了 a ^ b 的值。
步骤 2: b = a ^ b
接下来,将更新后的 a(即 a ^ b)和 b 进行异或运算,结果存回 b 中。由于 a = a ^ b,所以 b = (a ^ b) ^ b。根据异或的性质 a ^ a = 0 和 a ^ 0 = a,我们可以得到:b=a ,此时 b 存储了原来 a 的值。
步骤 3: a = a ^ b
最后,将更新后的 b(此时 b 是原来的 a)与 a(当前 a 存储的是 a ^ b)进行异或运算,结果存回 a 中。由于 a = a ^ b,且 b = a,所以:
根据异或的自反性 a ^ a = 0,所以 a = b,此时 a 存储了原来 b 的值。
到这里为止,我们通过三次异或运算完成了 a 和 b 的交换:
a 最终变成了原来 b 的值。
b 最终变成了原来 a 的值。
题目2:541. 反转字符串II
即:对字符串 s 中的每一个 2k 长度的子字符串,反转其前 k 个字符。如果剩下的字符不足 k 个,反转整个剩余部分。
// 用temp来交换数值
class Solution {
public String reverseStr(String s, int k) {
char[] ch = s.toCharArray();//由于字符串是不可变的,为了能够在原地修改字符,需要将其转化为字符数组 ch。
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(start < end){
char temp = ch[start];
ch[start] = ch[end];
ch[end] = temp;
start++;
end--;
}
}
return new String(ch);
}
}
for (int i = 0; i < ch.length; i += 2 * k) {
int start = i;
int end = Math.min(ch.length - 1, start + k - 1);
- i 是当前块的起始位置,每次增加 2k,确保按照每 2k 个字符进行分段。
- start 是当前块的起始位置。
- end 是该块的结束位置(但是我们只反转前 k 个字符)。Math.min(ch.length - 1, start + k - 1) 是为了确保 end 不会越界,即处理剩余字符时不会越界。
while (start < end) {
char temp = ch[start];
ch[start] = ch[end];
ch[end] = temp;
start++;
end--;
}
使用两个指针 start 和 end,从块的两端向中间反转字符。每次交换 ch[start] 和 ch[end],然后指针分别向中间移动。
return new String(ch);最后将字符数组转换回字符串并返回。
示例:
String s = "abcdefg";
int k = 2;
- 第一次循环,处理从 i = 0 到 i + 2k - 1 = 3 的字符:
反转前 k = 2 个字符,即 “ab” 反转为 “ba”,剩下 “cdefg”。 - 第二次循环,处理从 i = 4 到 i + 2k - 1 = 7 的字符:
反转前 k = 2 个字符,即 “cd” 反转为 “dc”,剩下 “efg”。 - 第三次循环,处理剩下的 “efg”,因为剩余字符少于 k,所以反转整个 “efg” 为 “gfe”。
最终输出:“bacdfeg”
时间复杂度分析
假设字符串长度为 n,我们每次处理 2k 长度的区块。
每个区块的反转操作需要 O(k) 时间,处理总共有 n / (2k) 个区块。
因此,总的时间复杂度为 O(n),即遍历字符串一次。
空间复杂度分析
由于我们只使用了常数级别的额外空间(除了字符数组之外),所以空间复杂度是 O(1)。
总结
这段代码通过分段反转字符,实现了题目要求的功能。每个 2k 长度的块中,反转前 k 个字符,剩余部分不动。它的时间复杂度为 O(n),空间复杂度为 O(1),是一个高效的解决方案。
题目3:替换数字
Java里的string不能修改,需要使用辅助空间。
import java.util.Scanner;
public class Main {
public static String replaceNumber(String s) {
int count = 0; // 统计数字的个数
int sOldSize = s.length();
for (int i = 0; i < s.length(); i++) {
if(Character.isDigit(s.charAt(i))){
count++;
}
}
// 扩充字符串s的大小,也就是每个空格替换成"number"之后的大小
char[] newS = new char[s.length() + count * 5];
int sNewSize = newS.length;
// 将旧字符串的内容填入新数组
System.arraycopy(s.toCharArray(), 0, newS, 0, sOldSize);
// 从后先前将空格替换为"number"
for (int i = sNewSize - 1, j = sOldSize - 1; j < i; j--, i--) {
if (!Character.isDigit(newS[j])) {
newS[i] = newS[j];
} else {
newS[i] = 'r';
newS[i - 1] = 'e';
newS[i - 2] = 'b';
newS[i - 3] = 'm';
newS[i - 4] = 'u';
newS[i - 5] = 'n';
i -= 5;
}
}
return new String(newS);
};
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
System.out.println(replaceNumber(s));
scanner.close();
}
}
解析:
public static String replaceNumber(String s) {
int count = 0; // count:用来统计字符串 s 中数字字符的个数。
int sOldSize = s.length();
for (int i = 0; i < s.length(); i++) {
if(Character.isDigit(s.charAt(i))){
count++;
}
}
通过遍历字符串 s,每次遇到一个数字字符 (Character.isDigit(s.charAt(i))),就将 count 加 1。
char[] newS = new char[s.length() + count * 5];
int sNewSize = newS.length;
newS:用来存放替换后的字符串。初始时,它的大小为原字符串 s 的长度加上 count * 5。
count * 5 是因为每个数字将被替换为 “number”,而 “number” 是 6 个字符,因此需要多分配 5 个位置来存放额外的字符。
sNewSize 是 newS 数组的大小。
System.arraycopy(s.toCharArray(), 0, newS, 0, sOldSize);
System.arraycopy 将原始字符串 s(转化为字符数组)复制到 newS 数组的前部分。
这一步的目的是将原字符串的内容保留下来,以便在后续处理中进行修改。
System.arraycopy 是 Java 中的一个系统级方法,用于快速地将一个数组的内容复制到另一个数组中。
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
- src:源数组,即要从中复制数据的数组。
- srcPos:源数组的起始位置,表示从源数组的哪个位置开始复制数据。
- dest:目标数组,数据将被复制到该数组中。
- destPos:目标数组的起始位置,表示目标数组从哪个位置开始接收数据。
- length:要复制的元素数量。
从后向前替换数字字符:
for (int i = sNewSize - 1, j = sOldSize - 1; j < i; j--, i--) {
if (!Character.isDigit(newS[j])) {
newS[i] = newS[j];
} else {
newS[i] = 'r';
newS[i - 1] = 'e';
newS[i - 2] = 'b';
newS[i - 3] = 'm';
newS[i - 4] = 'u';
newS[i - 5] = 'n';
i -= 5;
}
}
- i 和 j 分别是新数组和旧数组的指针,i 从新数组的尾部开始,j 从旧字符串的尾部开始。
- 每次循环:
如果 newS[j] 不是数字字符(!Character.isDigit(newS[j])),则直接将 newS[j] 复制到 newS[i]。
如果 newS[j] 是数字字符,则将其替换为 “number” 的字符。具体来说:
将 ‘r’ 放在 newS[i]。
将 ‘e’ 放在 newS[i-1]。
将 ‘b’ 放在 newS[i-2]。
将 ‘m’ 放在 newS[i-3]。
将 ‘u’ 放在 newS[i-4]。
将 ‘n’ 放在 newS[i-5]。 - 完成替换后,i 向前移动 5 位(i -= 5),跳过已经填充的 “number”。
main 方法:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
System.out.println(replaceNumber(s));
scanner.close();
}
- main 方法使用 Scanner 从标准输入读取一个字符串 s。
- 然后调用 replaceNumber 方法来处理字符串,并打印返回的结果。
- 最后,关闭 Scanner。