{{day08}}
Java 中字符串和数组的差别
1. 定义和本质
字符串 (String)
- 字符串在 Java 中是一个对象,是
java.lang.String
类的实例。 - 字符串实际上是一个字符数组的封装,但提供了更多的方法和功能。
- 字符串是不可变的(immutable),一旦创建就不能修改其内容。
数组 (Array)
- 数组是一种用于存储多个相同类型元素的容器。
- 数组是 Java 中的一种引用类型。
- 数组可以存储基本数据类型(如 int, char)或对象。
- 数组的大小是固定的,一旦创建就不能改变其长度。
2. 创建方式
字符串
String str1 = "Hello"; // 字面量创建
String str2 = new String("Hello"); // 使用构造函数
数组
int[] arr1 = new int[5]; // 创建指定大小的数组
int[] arr2 = {1, 2, 3, 4, 5}; // 使用初始化列表
3. 可变性
字符串
- 不可变:一旦创建,内容不能被改变。
- 每次"修改"字符串实际上是创建了一个新的字符串对象。
数组
- 可变:创建后可以修改其元素的值。
- 但数组的大小是固定的,不能增加或减少元素。
4. 操作和方法
字符串
- 提供了丰富的内建方法,如
length()
,substring()
,charAt()
,equals()
等。 - 支持连接操作(+)。
数组
- 基本操作如访问元素(使用索引)、修改元素。
- 没有内建方法,但可以使用
java.util.Arrays
类的静态方法进行操作。
题目信息 344. 反转字符串
- 题目链接: https://leetcode.cn/problems/reverse-string/description/
- 题目描述: 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组
s
的形式给出。
解法一: {{双指针——交换}}
解题思路
对于字符串,我们定义两个指针(也可以说是索引下标),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。
以字符串hello
为例,过程如下:
![[344.反转字符串.gif]]
代码实现
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--;
}
}
解法二: {{双指针——异或交换}}
解题思路
异或运算的一些重要性质:
- a ^ a = 0 (任何数与自身异或等于0)
- a ^ 0 = a (任何数与0异或等于它本身)
- a ^ b = b ^ a (交换律)
- (a ^ b) ^ c = a ^ (b ^ c) (结合律)
另外,异或运算是可逆的。如果 a ^ b = c,那么 c ^ b = a 和 c ^ a = b。
步骤1:s[l] ^= s[r]
这一步相当于 s[l] = s[l] ^ s[r],即 s[l] = a ^ b。 此时,s[l] = a ^ b,s[r] = b
步骤2:s[r] ^= s[l]
这一步相当于 s[r] = s[r] ^ s[l],即 s[r] = b ^ (a ^ b)。 根据异或的性质,b ^ (a ^ b) = (b ^ b) ^ a = 0 ^ a = a 此时,s[l] = a ^ b,s[r] = a
步骤3:s[l] ^= s[r]
这一步相当于 s[l] = s[l] ^ s[r],即 s[l] = (a ^ b) ^ a。 根据异或的性质,(a ^ b) ^ a = b 此时,s[l] = b,s[r] = a
至此,s[l] 和 s[r] 的值已经成功交换。
代码实现
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--;
}
}
题目信息541. 反转字符串 II
-
题目链接: https://leetcode.cn/problems/reverse-string-ii/description/
-
题目描述: 给定一个字符串
s
和一个整数k
,从字符串开头算起,每计数至2k
个字符,就反转这2k
字符中的前k
个字符。 -
如果剩余字符少于
k
个,则将剩余字符全部反转。 -
如果剩余字符小于
2k
但大于或等于k
个,则反转前k
个字符,其余字符保持原样。
解法一:
代码实现
public String reverseStr(String s, int k) {
// 将字符串转换为字符数组
char[] ch = s.toCharArray();
for (int i = 0;i < ch.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);
}
解法二:
代码实现
public String reverseStr(String s, int k) {
StringBuffer res = new StringBuffer();
int length = s.length();
int start = 0;
while (start < length) {
// 找到k处和2k处
StringBuffer temp = new StringBuffer();
// 与length进行判断,如果大于length了,那就将其置为length
int firstK = (start + k > length) ? length : start + k;
int secondK = (start + (2 * k) > length) ? length : start + (2 * k);
//无论start所处位置,至少会反转一次
temp.append(s.substring(start, firstK));
res.append(temp.reverse());
// 如果firstK到secondK之间有元素,这些元素直接放入res里即可。
if (firstK < secondK) { //此时剩余长度一定大于k。
res.append(s.substring(firstK, secondK));
}
start += (2 * k);
}
return res.toS
解法三: 异或
代码实现
public String reverseStr(String s, int k) {
char[] ch = s.toCharArray();
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){
ch[start] ^= ch[end];
ch[end] ^= ch[start];
ch[start] ^= ch[end];
start++;
end--;
}
}
return new String(ch);
}
题目信息 卡码网54. 替换数字(第八期模拟笔试)
- 题目链接: https://kamacoder.com/problempage.php?pid=1064
- 题目描述:给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。 例如,对于输入字符串 “a1b2c3”,函数应该将其转换为 “anumberbnumbercnumber”。
解题思路
首先扩充数组到每个数字字符替换成 “number” 之后的大小。
例如 字符串 “a5b” 的长度为3,那么 将 数字字符变成字符串 “number” 之后的字符串为 “anumberb” 长度为 8。
如图:
然后从后向前替换数字字符,也就是双指针法,过程如下:i指向新长度的末尾,j指向旧长度的末尾。
有同学问了,为什么要从后向前填充,从前向后填充不行么?
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素整体向后移动。
其实很多数组填充类的问题,其做法都是先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这么做有两个好处:
- 不用申请新数组。
- 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
代码实现(一)
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String s = in.nextLine();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (Character.isDigit(s.charAt(i))) {
sb.append("number");
}else sb.append(s.charAt(i));
}
System.out.println(sb);
}
代码实现(二)
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);
}