

前言:
昨天是周六,本来是说每天更新两个文章,一篇算法相关的,一篇后端的,但是我虽然早上写了两题算法,下午出去了,然后晚上回来学了会项目再把文章发出来就已经凌晨了,所以就留到周末来发这个算法题目了,主要内容就是关于字符串的相关算法。
给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。 例如,对于输入字符串 "a1b2c3",函数应该将其转换为 "anumberbnumbercnumber"。 对于输入字符串 "a5b",函数应该将其转换为 "anumberb" 输入:一个字符串 s,s 仅包含小写字母和数字字符。 输出:打印一个新的字符串,其中每个数字字符都被替换为了number 样例输入:a1b2c3 样例输出:anumberbnumbercnumber 数据范围:1 <= s.length < 10000。
首先拿到这个题目,显然就是字符串反转的问题,但是又多了一些别的逻辑处理
首先扩充数组到每个数字字符替换成 "number" 之后的大小。
例如 字符串 "a5b" 的长度为3,那么 将 数字字符变成字符串 "number" 之后的字符串为 "anumberb" 长度为 8。
然后从后向前替换数字字符,也就是双指针法,过程如下:i指向新长度的末尾,j指向旧长度的末尾。
为什么要从后向前填充,从前向后填充不行么?
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素整体向后移动。
其实很多数组填充类的问题,其做法都是先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这里需要注意的是,我们用的是java,java底层的字符串是不可变的,所以我们要转成字符数组进行处理。之后我们将这个转成字符数组拷贝到新的已经扩容的字符数组中,然后进行一系列的反转。
char[] newS = new char[s.length() + count * 5];关于这个count,也就是我们通过遍历得到字符串中有几个数字,从而count++,一个count对应五个位置。
整体演示,比较好理解
原字符串: a 1 b
索引: 0 1 2
新数组(初始): a 1 b _ _ _ _ _
索引: 0 1 2 3 4 5 6 7
从后向前处理:
第1步: 复制'b' → b
索引: 7
第2步: 遇到'1' → 填入 number(反向)
填入: n u m b e r
索引: 1 2 3 4 5 6
最终结果: a n u m b e r b
0 1 2 3 4 5 6 7
转为字符串: "anumberb"主要还是要判断哪个位置是数字,然后从后往前填充,至于为什么这样,因为如果是从前往后,每次添加元素都要将添加元素之后的所有元素整体向后移动。
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);
}text
原长度 = 3
遇到数字'1' → len += 5 → len = 8java
char[] ret = new char[8]; // [_, _, _, _, _, _, _, _]
// 复制原字符串
ret = ['a', '1', 'b', _, _, _, _, _]
0 1 2 3 4 5 6 7java
i = 2, j = 7第1次循环 (i=2):ret[2] = 'b' 不是数字
java
ret[7] = 'b'
j = 6结果:[a, 1, b, _, _, _, _, b]
第2次循环 (i=1):ret[1] = '1' 是数字
java
ret[6] = 'r' // j=6→5
ret[5] = 'e' // j=5→4
ret[4] = 'b' // j=4→3
ret[3] = 'm' // j=3→2
ret[2] = 'u' // j=2→1
ret[1] = 'n' // j=1→0结果:[a, n, u, m, b, e, r, b]
第3次循环 (i=0):ret[0] = 'a' 不是数字
java
ret[0] = 'a' // j=0→ -1整体的逻辑更清晰易懂
i 只管遍历原内容,j 只管填充新位置
j < i 这种边界条件
结语:
其实在写算法题的时候,我们要养成手撕的习惯,这对我们以后的面试帮助很大,刚开始肯定很难,有时候连方法的格式,返回值什么的都写的模糊不清,但只要坚持下来,会收获很大。 如果对你有帮助,请点赞,关注,收藏,你的支持就是我最大的鼓励!