算法通关村第十二关——字符串经典基础面试题(白银)
1 反转的问题
1.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;
}
}
}
1.2 反转字符串 II
这道题比较烦的就是逻辑处理这块:
- 如果剩余字符少于
k
个,则将剩余字符全部反转。 - 如果剩余字符小于
2k
但大于或等于k
个,则反转前k
个字符,其余字符保持原样。
class Solution {
public String reverseStr(String s, int k) {
int n = s.length();
char[] arr = s.toCharArray();
for (int i = 0; i < n; i += 2 * k) {
reverse(arr, i, Math.min(i + k, n) - 1);
}
return new String(arr);
}
public void reverse(char[] arr, int left, int right) {
while (left < right) {
char temp = arr[left];
arr[left++] = arr[right];
arr[right--] = temp;
}
}
}
i 表示当前要进行反转的子字符串的起始位置。由于每次要反转的子字符串包括前 k 个字符,因此起始位置为 i。
Math.min(i + k, n) - 1 用来确定子字符串的结束位置。
-
其中,i + k 表示子字符串的最大可能结束位置,n 是原始字符串的长度。
-
如果 i + k 的值小于 n,则说明当前子字符串的长度不足 k,可以直接取 i + k 作为结束位置。
-
否则,即 i + k >= n,说明当前子字符串的长度超过等于 k,应该只取到 n - 1 作为结束位置,以保证不越界。
1.3 仅仅反转字母
这道题就是最简单的反转字符串多了一个判断
class Solution {
public String reverseOnlyLetters(String s) {
int left = 0;
int right = s.length() - 1;
char[] ch = s.toCharArray();
while (left < right) {
if (!Character.isLetter(ch[left])) {
left++;
} else if (!Character.isLetter(ch[right])) {
right--;
} else {
char temp = ch[left];
ch[left++] = ch[right];
ch[right--] = temp;
}
}
return new String(ch);
}
}
1.4 反转字符串中的单词
方法一:使用api
没什么好说的
class Solution {
public String reverseWords(String s) {
if(s == null || s.length() == 0){
return s;
}
// 去除两边的空格
s = s.trim();
// 根据正则表达式按照空格进行分割添加
List<String> wordList = Arrays.asList(s.split("\\s+"));
// 翻转集合
Collections.reverse(wordList);
// 将集合的数据按照空格进行拼接
return String.join(" ", wordList);
}
}
方法二:自己实现上面写的功能
注意:不要使用辅助空间,空间复杂度要求为O(1)。
不能使用辅助空间之后,那么只能在原字符串上下功夫了。
想一下,我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒序了,那么再把单词反转一下,单词不就正过来了。
所以解题思路如下:
- 移除多余空格
- 将整个字符串反转
- 将每个单词反转
举个例子,源字符串为:"the sky is blue "
- 移除多余空格 : “the sky is blue”
- 字符串反转:“eulb si yks eht”
- 单词反转:“blue is sky the”
这样我们就完成了翻转字符串里的单词。
class Solution {
public String reverseWords(String s) {
// 1.去除首尾以及中间多余空格
StringBuilder sb = removeSpace(s);
// 2.反转整个字符串
reverseString(sb, 0, sb.length() - 1);
// 3.反转各个单词
reverseEachWord(sb);
return sb.toString();
}
public StringBuilder removeSpace(String s){
int start = 0;
int end = s.length() - 1;
while (s.charAt(start) == ' ') start++;
while (s.charAt(end) == ' ') end--;
StringBuilder sb = new StringBuilder();
while (start <= end) {
char c = s.charAt(start);
if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
sb.append(c);
}
start++;
}
return sb;
}
public void reverseString(StringBuilder sb, int start, int end) {
while (start < end) {
char temp = sb.charAt(start);
sb.setCharAt(start, sb.charAt(end));
sb.setCharAt(end, temp);
start++;
end--;
}
}
private void reverseEachWord(StringBuilder sb) {
int start = 0;
int end = 1;
int n = sb.length();
while (start < n) {
while (end < n && sb.charAt(end) != ' ') {
end++;
}
reverseString(sb, start, end - 1);
start = end + 1;
end = start + 1;
}
}
}
2 验证回文串
方法一:分步实现
这题不难,也就是先把string的大写变成小写,保留数字,其他的去掉,然后再进行判断
那也就是分为两步即可
lass Solution {
public boolean isPalindrome(String s) {
String sb = toLowerCase(s);
int left = 0;
int right = sb.length() - 1;
while(left < right){
if(sb.charAt(left++) != sb.charAt(right--)){
return false;
}
}
return true;
}
public String toLowerCase(String s){
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= 'A' && c <= 'Z') {
sb.append((char) (c + 32));
} else if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
sb.append(c);
}
}
return sb.toString();
}
}
这样是分成两步来完成,不过也可以一步到位
方法二:一步到位
思想:
-
可以使用双指针的方法,同时遍历字符串的左右两端进行比较。
-
如果字符不相等,则直接返回false;如果字符相等,则继续向中间靠拢。
在该方法中,我们使用了Character.isLetterOrDigit()来判断字符是否是字母或数字,并使用Character.toLowerCase()将字符转换为小写字母进行比较。通过逐步移动左右指针来比较字符,如果有不相等的情况,直接返回false;如果遍历完字符串没有返回false的情况,则返回true,表示字符串是回文。
class Solution {
public boolean isPalindrome(String s) {
int left = 0;
int right = s.length() - 1;
while (left < right) {
char c1 = s.charAt(left);
char c2 = s.charAt(right);
if (!Character.isLetterOrDigit(c1)) {
left++;
} else if (!Character.isLetterOrDigit(c2)) {
right--;
} else {
if (Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
return false;
}
left++;
right--;
}
}
return true;
}
}
3 字符串中的第一个唯一字符
思路:
- 遍历字符串,记录每个字符的个数
- 查看每个字符的个数,如果为1,就是唯一字符,并且顺序查看时,第一个就是第一个唯一字符
那么根据这个思路,就需要一个数据结构能够记录字符的个数,还能够顺序存储,所以使用LinkedHashMap
算法如下:
class Solution {
public int firstUniqChar(String s) {
HashMap<Character, Integer> map = new LinkedHashMap<>();
for (char c : s.toCharArray()) {
map.put(c, map.getOrDefault(c, 0) + 1);
}
for (char c : map.keySet()) {
if (map.get(c) == 1) {
return s.indexOf(c);
}
}
return -1;
}
}
当然使用hashmap也可以,就是慢很多,这里也附上:
class Solution {
public int firstUniqChar(String s) {
Map<Character, Integer> frequency = new HashMap<Character, Integer>();
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;
}
}
4 有效的字母异位词
方法1:哈希
这题跟上一天几乎一样,只不过这一题多一个string,那么只要减去第二个string存在的个数,再判断map里的keyvalue是否为0即可
class Solution {
public boolean isAnagram(String s, String t) {
HashMap<Character, Integer> map = new LinkedHashMap();
for(char c : s.toCharArray()){
map.put(c, map.getOrDefault(c, 0) + 1);
}
for(char c : t.toCharArray()){
if(!map.containsKey(c)){
return false;
}
map.put(c, map.get(c) - 1);
}
for (char key : map.keySet()) {
if(map.get(key) != 0){
return false;
}
}
return true;
}
}
方法2:数组
通过使用固定大小的数组来统计字符出现的次数,这种方法避免了使用哈希表的额外空间开销,并提高了时间复杂度。
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] count = new int[26];
for (char c : s.toCharArray()) {
count[c - 'a']++;
}
for (char c : t.toCharArray()) {
count[c - 'a']--;
}
for (int i : count) {
if (i != 0) {
return false;
}
}
return true;
}
}
方法3:排序数组
如果两个字符串是字母异位词,那么它们排序后的结果应该是相同的。
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
Arrays.sort(sChars);
Arrays.sort(tChars);
return Arrays.equals(sChars, tChars);
}
}