【LeetCode刷题日记】:字符串替换技巧揭秘
🔥个人主页:北极的代码(欢迎来访)
🎬作者简介:java后端学习者
❄️个人专栏:苍穹外卖日记,SSM框架深入,JavaWeb
✨命运的结局尽可永在,不屈的挑战却不可须臾或缺!
前言:
昨天是周六,本来是说每天更新两个文章,一篇算法相关的,一篇后端的,但是我虽然早上写了两题算法,下午出去了,然后晚上回来学了会项目再把文章发出来就已经凌晨了,所以就留到周末来发这个算法题目了,主要内容就是关于字符串的相关算法。
题目背景:卡码网 替换数字
给定一个字符串 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(); } }总结
这个算法的核心思路:
先创建足够大的新数组
复制原内容到新数组前半部分
从后向前处理:遇到非数字直接复制,遇到数字则从后往前写入 "number"
覆盖原数字位置:因为 "number" 比 "1" 长,必须向后延伸,所以从后向前处理可以避免覆盖未处理的内容
第二种写法:
// 为了还原题目本意,先把原数组复制到扩展长度后的新数组,然后不再使用原数组、原地对新数组进行操作。 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 = 8步骤2:创建新数组并复制
java char[] ret = new char[8]; // [_, _, _, _, _, _, _, _] // 复制原字符串 ret = ['a', '1', 'b', _, _, _, _, _] 0 1 2 3 4 5 6 7步骤3:从后向前填充
java 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这种边界条件正确性保证:每个数字固定多占5个位置,指针移动步数确定
结语:
其实在写算法题的时候,我们要养成手撕的习惯,这对我们以后的面试帮助很大,刚开始肯定很难,有时候连方法的格式,返回值什么的都写的模糊不清,但只要坚持下来,会收获很大。
如果对你有帮助,请点赞,关注,收藏,你的支持就是我最大的鼓励!
