官方:对于今天这些题目一些同学直接用一个库函数 reverse,调一下直接完事了, 相信每一门编程语言都有这样的库函数。如果这么做题的话,这样大家不会清楚反转字符串的实现原理了。
但是也不是说库函数就不能用,是要分场景的。
如果在现场面试中,我们什么时候使用库函数,什么时候不要用库函数呢?
如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数。
毕竟面试官一定不是考察你对库函数的熟悉程度, 如果使用python和java 的同学更需要注意这一点,因为python、java提供的库函数十分丰富。
如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。
建议大家平时在leetcode上练习算法的时候本着这样的原则去练习,这样才有助于我们对算法的理解。
一、反转字符串
1.题目描述
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 1:
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:
输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
2.思路
1.常规思路(双指针)
利用一个temp实现反转,这个很常见,我一看题目也是用的这个方法
2.异或运算(不使用额外的临时变量,可以节省一点内存空间)
异或运算(^
)有三个重要特性:
- 任何数和自身异或结果为 0(
a ^ a = 0
) - 任何数和 0 异或结果还是自身(
a ^ 0 = a
) - 异或运算满足交换律和结合律(
a ^ b ^ c = a ^ (b ^ c)
)
假设当前l
和r
指向的字符分别为a
和b
:
-
第一步:
s[l] ^= s[r]
- 等价于
s[l] = s[l] ^ s[r]
- 此时
s[l]
的值变为a ^ b
,s[r]
仍为b
- 等价于
-
第二步:
s[r] ^= s[l]
- 等价于
s[r] = s[r] ^ s[l]
- 代入值后:
s[r] = b ^ (a ^ b) = a
(根据异或特性) - 此时
s[r]
的值变为a
,s[l]
仍为a ^ b
- 等价于
-
第三步:
s[l] ^= s[r]
- 等价于
s[l] = s[l] ^ s[r]
- 代入值后:
s[l] = (a ^ b) ^ a = b
(根据异或特性) - 此时
s[l]
的值变为b
,s[r]
为a
,完成交换
- 等价于
-
最后移动指针:
l++
(左指针右移),r--
(右指针左移)
3.代码
1.常规思路
class Solution {
public void reverseString(char[] s) {
int left = 0;
int right = s.length-1;
while (left<right){
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
}
2.异或运算
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--;
}
}
}
二、反转字符串II
1.题目描述
给定一个字符串 s 和一个整数 k,从字符串开头算起, 每计数至 2k 个字符,就反转这 2k 个字符中的前 k 个字符。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
示例:
输入: s = "abcdefg", k = 2
输出: "bacdfeg"
2.思路
1.最容易理解思路
- 分组规则:以
2k
为步长遍历字符串(i += 2 * k
),把字符串分成若干段 - 处理每组:对每段的前
k
个字符进行反转 - 边界兼容:当剩余字符不足
k
或2k
时,按规则处理
我再看到之后还是想用for循环和temp进行编写,每 2k 个字符为一组,反转每组前 k 个字符"的规则处理字符串,其中我发现一个官方很好的方法int end = Math.min(最大索引,
理想情况下要反转的终点索引);
这行代码堪称点睛之笔,它的巧妙之处在于用一行行代码同时处理了所有边界情况,具体代码可以看3.代码中的完整代码!我觉得真的很巧妙,我自己写的时候为了这个多余的几个数,写了if很多条语句,这个就是一行就完事了!!!!
2.定义反转函数
这个直接看完整代码就可以啦
3.代码
1.最容易理解的思路
class Solution {
public String reverseStr(String s, int k) {
char[] ch = s.toCharArray();
for (int i = 0; i < s.length(); i+=2*k) {
int start = i;
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);
}
}
代码: int end = Math.min(ch.length - 1,start + k - 1);//巧妙之处
start
是当前区间的起始索引(每次循环从 i
开始,步长为 2k
)
start + k - 1
是「理想情况下」要反转的终点索引(即从 start
开始数 k
个字符的位置)
ch.length - 1
是字符数组的最后一个索引(避免越界)
Math.min(a, b)
取两者中的较小值作为实际终点
我写到这里好兴奋!!!!!就是一下知道了这个一行代码 的思路就很巧妙哈哈哈哈!!!
就是不用写很多代码去完成不足2k的部分,真巧妙啊
2.定义反转函数
class Solution {
public String reverseStr(String s, int k) {
char[] ch = s.toCharArray();
// 1. 每隔 2k 个字符的前 k 个字符进行反转
for (int i = 0; i< ch.length; i += 2 * k) {
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
if (i + k <= ch.length) {
reverse(ch, i, i + k -1);
continue;
}
// 3. 剩余字符少于 k 个,则将剩余字符全部反转
reverse(ch, i, ch.length - 1);
}
return new String(ch);
}
// 定义翻转函数
public void reverse(char[] ch, int i, int j) {
for (; i < j; i++, j--) {
char temp = ch[i];
ch[i] = ch[j];
ch[j] = temp;
}
}
}
三、替换数字
1.题目描述
给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。
例如,对于输入字符串 "a1b2c3",函数应该将其转换为 "anumberbnumbercnumber"。
对于输入字符串 "a5b",函数应该将其转换为 "anumberb"
输入:一个字符串 s,s 仅包含小写字母和数字字符。
输出:打印一个新的字符串,其中每个数字字符都被替换为了number
样例输入:a1b2c3
样例输出:anumberbnumbercnumber
数据范围:1 <= s.length < 10000。
2.思路
我自己的这个思路还不错我感觉,不用一个字符一个字符添加number比如
newS[i] = 'r';
newS[i - 1] = 'e';
newS[i - 2] = 'b';
newS[i - 3] = 'm';
newS[i - 4] = 'u';
newS[i - 5] = 'n';
我感觉有点繁琐,接下来是我的思路 但是第一版写完后我发现有报错,成功改正:(请看VCR)
初始思路(第一版错误代码)
-
核心目标:将输入字符串中的每个数字字符替换为 "number",其他字符保持不变
-
实现计划:
- 读取用户输入的字符串
- 创建一个足够大的字符数组(长度为原字符串的 6 倍,因为 "number" 是 6 个字符)
- 遍历输入字符串的每个字符
- 若为数字,则将 "number" 逐个字符存入新数组;否则直接存入原字符
- 输出处理后的结果
-
遇到的报错:
- 使用
for (char ch : input)
遍历字符串时出现错误 - 原因:Java 中字符串(String)不能直接用增强 for 循环遍历,增强 for 循环需要遍历的是数组或实现了 Iterable 接口的集合类
- 使用
改进思路(第二版最终代码)
- 问题定位:明确错误是因为字符串不能直接用于增强 for 循环遍历
- 解决方案:
- 新增一步:将字符串转换为字符数组
char[] input = in.toCharArray()
- 用转换后的字符数组进行增强 for 循环遍历
for (char ch : input)
- 新增一步:将字符串转换为字符数组
3.代码
自己的实现代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String in = sc.next();
char[] input = in.toCharArray();
char[] input1 = new char[in.length() * 6]; // 预留足够空间
int index = 0; // 用于追踪input1数组的当前位置
for (char ch : input) {
if ('0' <= ch && ch <= '9') {
// 如果是数字字符,转换为"number"并逐个存入数组
String number = "number";
for (int i = 0; i < number.length(); i++) {
input1[index++] = number.charAt(i);
}
} else {
// 非数字字符直接存入数组
input1[index++] = ch;
}
}
System.out.println(new String(input1, 0, index));
}
}
开心滴锣鼓!!!!
官方代码
解法一:
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();
}
}
解法二
// 为了还原题目本意,先把原数组复制到扩展长度后的新数组,然后不再使用原数组、原地对新数组进行操作。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.next();
int len = s.length();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) >= 0 && s.charAt(i) <= '9') {
len += 5;
}
}
char[] ret = new char[len];
for (int i = 0; i < s.length(); i++) {
ret[i] = s.charAt(i);
}
for (int i = s.length() - 1, j = len - 1; i >= 0; i--) {
if ('0' <= ret[i] && ret[i] <= '9') {
ret[j--] = 'r';
ret[j--] = 'e';
ret[j--] = 'b';
ret[j--] = 'm';
ret[j--] = 'u';
ret[j--] = 'n';
} else {
ret[j--] = ret[i];
}
}
System.out.println(ret);
}
}
四、个人心得(真实)
今天一整个大开心,大开心!!!先学习反转字符串II时候,发现官方的的代码一行可以代替我绞尽脑汁想出来很多if语句处理不足2k的数字,很开心了,然后最后替换是数字,自己的思路就少写了很多条代码,也学习了官方代码,则么说我也不知道时间复杂度怎么个比较,反正很开心,动力十足!!!