字符串基础题

通关进度

题目 说明
转换成小写字母 通关
字符串转换整数 通关

参考资料

字符串与常见面试题

转换成小写字母

709. 转换成小写字母

问题

【LeetCode 709】:给你一个字符串 s ,将该字符串中的大写字母转换成相同的小写字母,返回新的字符串。

ASCII

  • 大写字母 A - Z 的 ASCII 码范围为 $[65,90]$
  • 小写字母 a - z 的 ASCII 码范围为 $[97,122]$

如果 ch 的 ASCII 码在 $[65,90]$ 的范围内,那么将它的 ASCII 码增加 $32$,即可得到对应的小写字母。

由于 $[65,90]$ 对应的二进制表示为 $[(01000001)_2,(01011010)_2]$, $32$ 对应的二进制表示为 $(00100000)_2$,而对于 $[(01000001)_2,(01011010)_2]$ 内的所有数,表示 $32$ 的那个二进制位都是 0,因此可以对 ch 的 ASCII 码与 $32$ 做按位或运算,替代与 $32$ 的加法运算。

1
2
3
4
5
6
7
8
9
10
11
12
public String toLowerCase(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (ch >= 65 && ch <= 90) {
// 【特殊情况转换】ch = ch + 32
ch |= 32;
}
sb.append(ch);
}
return sb.toString();
}

字符串转换整数

8. 字符串转换整数 (atoi)

问题

【LeetCode 8】:请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public int myAtoi(String s) {

char[] chars = s.toCharArray();

// 1. 读入字符串并丢弃无用的前导空格
int index = 0;
while (index < s.length() && chars[index] == ' ') {
index++;
}
// 【极端情况】{" "}
if (index == s.length()) {
// 如果没有读入数字,则整数为 0
return 0;
}

// 2. 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。
// 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
int sign = 1;
char firstChar = chars[index];
if (firstChar == '+') {
index++;
} else if (firstChar == '-') {
index++;
sign = -1;
}

// 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾
// 3. 将后续出现的数字字符进行转换
int ans = 0;
while (index < s.length()) {
char curChar = chars[index];
// 3.1 合法性校验
if (curChar > '9' || curChar < '0') {
break;
}
// 3.2 溢出判断
if (ans > Integer.MAX_VALUE / 10 ||
(ans == Integer.MAX_VALUE / 10 && (curChar - '0') > Integer.MAX_VALUE % 10)) {
return Integer.MAX_VALUE;
}
if (ans < Integer.MIN_VALUE / 10 ||
(ans == Integer.MIN_VALUE / 10 && (curChar - '0') > -(Integer.MIN_VALUE % 10))) {
return Integer.MIN_VALUE;
}
ans = ans * 10 + sign * (curChar - '0');
index++;
}
return ans;

}

字符串经典题

通关进度

题目 说明
字符串反转问题 通关
回文串问题 通关
搜索第一个唯一字符 通关
有效的字母异位词 通关

字符串反转专题

反转字符串

344. 反转字符串

问题

【LeetCode 344】:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

1
2
3
4
5
6
7
8
public void reverseString(char[] s) {

for (int left = 0, right = s.length - 1; left < right; left++, right--) {
char temp = s[left];
s[left] = s[right];
s[right] = temp;
}
}

K 组反转字符串

541. 反转字符串 II

问题

【LeetCode 541】:给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public String reverseStr(String s, int k) {

char[] chars = s.toCharArray();
// OFFSET 2 * K
for (int i = 0; i < chars.length; i += 2 * k) {
reverse(chars, i, Math.min(i + k, chars.length) - 1);
}
return new String(chars);

}

public void reverse(char[] arr, int left, int right) {
while (left < right) {
char temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
}

仅反转字母

917. 仅仅反转字母

问题

【LeetCode 917】:给你一个字符串 s ,根据下述规则反转字符串:

  • 所有非英文字母保留在原有位置。
  • 所有英文字母(小写或大写)位置反转。
    返回反转后的 s

双指针

使用 left 指针从左边开始扫描字符串 sright 指针从右边开始扫描字符串 s。如果两个指针都扫描到字母,且 left<right,那么交换 s[left]s[right],然后继续进行扫描;否则表明反转过程结束,返回处理后的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public String reverseOnlyLetters(String s) {

char[] arr = s.toCharArray();
int left = 0, right = s.length() - 1;
while (left < right) {
// 判断左边是否扫描到字母
while (left < right && !Character.isLetter(s.charAt(left))) {
left++;
}
// 判断右边是否扫描到字母
while (left < right && !Character.isLetter(s.charAt(right))) {
right--;
}
swap(arr, left, right);
left++;
right--;
}
return new String(arr);
}

public void swap(char[] arr, int left, int right) {
char temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}

反转字符串里的单词

151. 反转字符串中的单词

问题

【LeetCode 151】:给你一个字符串 s ,请你反转字符串中 单词 的顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public String reverseWords(String s) {


StringBuilder sb = trimSpace(s);

// 翻转字符串
reverse(sb, 0, sb.length() - 1);

// 翻转每个单词
reverseEachWord(sb);

return sb.toString();
}

public StringBuilder trimSpace(String str) {
int left = 0, right = str.length() - 1;
// 去掉字符串开头的空白字符
while (left <= right && str.charAt(left) == ' ') {
++left;
}
// 去掉字符串末尾的空白字符
while (left <= right && str.charAt(right) == ' ') {
--right;
}
// 将字符串间多余的空白字符去除
StringBuilder sb = new StringBuilder();
while (left <= right) {
char ch = str.charAt(left);

if (ch != ' ') {
sb.append(ch);
} else if (sb.charAt(sb.length() - 1) != ' ') {
sb.append(ch);
}
++left;
}
return sb;
}

public void reverse(StringBuilder sb, int left, int right) {
while (left < right) {
char tmp = sb.charAt(left);
sb.setCharAt(left++, sb.charAt(right));
sb.setCharAt(right--, tmp);
}
}

public void reverseEachWord(StringBuilder sb) {
int n = sb.length();
int start = 0, end = 0;

while (start < n) {
// 找到单词
while (end < n && sb.charAt(end) != ' ') {
++end;
}
// 翻转单词
reverse(sb, start, end - 1);
// 更新start,去找下一个单词
start = end + 1;
++end;
}

}

验证回文串

125. 验证回文串

问题

【LeetCode 125】:如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。

直接在原字符上判断

  • 直接在原字符串 s 上使用双指针
  • 在移动任意一个指针时,需要不断地向另一指针的方向移动,直到遇到一个字母或数字字符,或者两指针重合为止
  • 每次将指针移到下一个字母字符或数字字符,再判断这两个指针指向的字符是否相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;

while (left < right) {
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) {
++left;
}
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) {
--right;
}
if (left < right) {
if (Character.toLowerCase(s.charAt(left)) !=
Character.toLowerCase(s.charAt(right))) {
return false;
}
++left;
--right;
}
}
return true;

}

字符串中的第一个唯一字符

387. 字符串中的第一个唯一字符

问题

【LeetCode 387】:给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1

使用哈希表存储频数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int firstUniqChar(String s) {

Map<Character, Integer> frequency = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
for (int i = 0; i < s.length(); i++) {
if (frequency.get(s.charAt(i)) == 1) {
return i;
}
}
return -1;
}

有效的字母异位词

242. 有效的字母异位词

问题

【LeetCode 242】:给定两个字符串 st ,编写一个函数来判断 t 是否是 s 的字母异位词。

排序

1
2
3
4
5
6
7
8
9
10
11
12
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] chars1 = s.toCharArray();
char[] chars2 = t.toCharArray();

Arrays.sort(chars1);
Arrays.sort(chars2);

return Arrays.equals(chars1, chars2);
}

哈希表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] frequency = new int[26];
for (int i = 0; i < s.length(); i++) {
// 小写字母频次
frequency[s.charAt(i) - 'a']++;
}

for (int i = 0; i < t.length(); i++) {
frequency[t.charAt(i) - 'a']--;
if (frequency[t.charAt(i) - 'a'] < 0) {
return false;
}
}
return true;
}

字符串冲刺题

通关进度

题目 说明
最长公共前缀问题 通关
字符串压缩问题 通关
表示数值的字符串 通关

最长公共前缀

14. 最长公共前缀

问题

【LeetCode 14】:编写一个函数来查找字符串数组中的最长公共前缀。

横向扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
String prefix = strs[0];
int count = strs.length;
for (int i = 1; i < count; i++) {
// 求任意两个字符串的公共前缀
// LCP(0,1)->LCP(0,2)
prefix = longestCommonPrefix(prefix, strs[i]);
if (prefix.length() == 0) {
break;
}
}
return prefix;
}

public String longestCommonPrefix(String str1, String str2) {
int len = Math.min(str1.length(), str2.length());
int index = 0;
while (index < len) {
if (str1.charAt(index) != str2.charAt(index)) {
break;
}
index++;
}
return str1.substring(0, index);
}

纵向扫描


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 纵向扫描
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
// 第一个单词长度
int length = strs[0].length();
// strs 数组长度
int count = strs.length;

// 横向走
for (int i = 0; i < length; i++) {
char ch = strs[0].charAt(i);
// 纵向走
for (int j = 1; j < count; j++) {
if (i == strs[j].length() || strs[j].charAt(i) != ch) {
return strs[0].substring(0,i);
}
}
}
return strs[0];
}

分治

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 分治
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
} else {
return longestCommonPrefix(strs, 0, strs.length - 1);
}
}

public String longestCommonPrefix(String[] strs, int start, int end) {
if (start == end) {
return strs[start];
}

int mid = start + ((end - start) >> 1);
String lcpLeft = longestCommonPrefix(strs, start, mid);
String lcpRight = longestCommonPrefix(strs, mid + 1, end);
return commonPrefix(lcpLeft, lcpRight);
}

public String commonPrefix(String lcpLeft, String lcpRight) {
int minLength = Math.min(lcpLeft.length(), lcpRight.length());
for (int i = 0; i < minLength; i++) {
if (lcpLeft.charAt(i) != lcpRight.charAt(i)) {
return lcpLeft.substring(0, i);
}
}
return lcpLeft.substring(0, minLength);
}

字符串压缩问题

443. 压缩字符串

问题

【LeetCode 443】:给你一个字符数组 chars ,请使用下述算法压缩:

从一个空字符串 s 开始。对于 chars 中的每组 连续重复字符 :

  • 如果这一组长度为 1 ,则将字符追加到 s
  • 否则,需要向 s 追加字符,后跟这一组的长度
    压缩后得到的字符串 s 不应该直接返回 ,需要转储到字符数组 chars 中。需要注意的是,如果组长度为 1010 以上,则在 chars 数组中会被拆分为多个字符。

请在 修改完输入数组后 ,返回该数组的新长度。

双指针

  • 可以使用双指针分别标志我们在字符串中读和写的位置
  • 每次当读指针 read 移动到某一段连续相同子串的最右侧
  • 就在写指针 write 处依次写入该子串对应的字符和子串长度即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public int compress(char[] chars) {
int write = 0, left = 0;

for (int read = 0; read < chars.length; read++) {
// ['a', ..., 'a']
if (read == chars.length - 1 // 末尾
|| chars[read] != chars[read + 1]) {// 重复字符的最右侧
// ['a']
chars[write++] = chars[read];
int num = read - left + 1;
// num = 12
if (num > 1) {
int anchor = write;
while (num > 0) {
// ['a', '2', '1']
chars[write++] = (char) (num % 10 + '0');
num /= 10;
}
// reverse ['2', '1']
reverse(chars, anchor, write - 1);
}
left = read + 1;
}
}
return write;
}

public void reverse(char[] chars, int left, int right) {
while (left < right) {
char temp = chars[left];
chars[left] = chars[right];
chars[right] = temp;
left++;
right--;
}
}

有效数字

LCR 138. 有效数字

问题

【LCR 138. 有效数字】:有效数字(按顺序)可以分成以下几个部分:

  1. 若干空格
  2. 一个 小数 或者 整数
  3. (可选)一个 'e''E' ,后面跟着一个 整数
  4. 若干空格