题目:
151.翻转字符串里的单词
建议:这道题目基本把 刚刚做过的字符串操作 都覆盖了,不过就算知道解题思路,本题代码并不容易写,要多练一练。
给定一个字符串,逐个翻转字符串中的每个单词。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
题目链接/文章讲解/视频讲解:https://programmercarl.com/0151.翻转字符串里的单词.html
卡码网:55.右旋转字符串
建议:题解中的解法如果没接触过的话,应该会想不到
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。
输入:输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。
输出:输出共一行,为进行了右旋转操作后的字符串。
样例输入:
2
abcdefg
样例输出:
fgabcde
数据范围:1 <= k < 10000, 1 <= s.length < 10000;
题目链接/文章讲解:
答案
/**
* String2类
* 该类包含多种字符串处理方法,主要用于字符串的反转、去除多余空格和单词顺序调整等操作
* 实现了LeetCode上的多个字符串处理题目
*/
public class String2 {
/**
* 翻转字符串的指定部分
* 区间写法:左闭右闭 [start, end]
* 使用双指针法,从两端向中间移动,交换对应位置的字符
* 时间复杂度:O(n),其中n是需要反转的字符数量
* 空间复杂度:O(1),只需要常数级别的额外空间
*
* @param s 需要翻转的字符数组
* @param start 起始位置(包含)
* @param end 结束位置(包含)
*/
public static void reverse(char[] s, int start, int end) {
// 双指针从两端向中间移动
for (int i = start, j = end; i < j; i++, j--) {
// 交换两端的字符
char temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
/**
* 去除字符串中多余的空格
* 保证单词之间只有一个空格,且字符串首尾没有空格
* 使用快慢指针法
* 时间复杂度:O(n),其中n是字符串的长度
* 空间复杂度:O(n),需要创建一个字符数组
*
* @param s 原始字符串
* @return 处理后的字符串
*/
public static String removeExtraSpaces(String s) {
// 将字符串转换为字符数组,便于操作单个字符
char[] chars = s.toCharArray();
// 慢指针,用于控制新字符串的写入位置
// 最终slow的值就是新字符串的长度
int slow = 0; // 整体思想参考移除元素的算法
// 快指针i用于遍历原始字符串
for (int i = 0; i < chars.length; ++i) {
// 当遇到非空格字符时开始处理
if (chars[i] != ' ') {
// 如果不是第一个单词(slow不为0),则在单词前添加一个空格
// 这确保了单词之间只有一个空格
if (slow != 0) {
// 在新字符串中添加一个空格,然后slow向后移动一位
chars[slow++] = ' ';
}
// 处理当前单词:将整个单词复制到新位置
// 持续复制直到遇到空格或字符串结束
while (i < chars.length && chars[i] != ' ') {
// 将当前字符复制到slow位置,然后两个指针都向后移动
chars[slow++] = chars[i++];
}
// 因为for循环结束后会执行i++,而我们在while循环中已经增加了i
// 为了不跳过字符,这里需要i--来抵消for循环的i++
i--;
}
// 如果当前字符是空格,快指针i直接跳过(不做任何处理)
// 这样就跳过了所有多余的空格
}
// 返回处理后的字符串,只取前slow个字符
// 因为slow正好是去除多余空格后的新字符串长度
return new String(chars, 0, slow);
}
/**
* LeetCode 151. 翻转字符串中的单词
* 给定一个字符串,逐个翻转字符串中的每个单词
* 例如:"the sky is blue" -> "blue is sky the"
*
* 算法思路:
* 1. 去除多余空格
* 2. 反转整个字符串
* 3. 再反转每个单词
*
* 时间复杂度:O(n),其中n是字符串的长度
* 空间复杂度:O(n),需要创建一个字符数组
*
* @param s 原始字符串
* @return 翻转单词后的字符串
*/
public static String reverseWords(String s) {
// 去除多余空格,保证单词之间只有一个空格,且字符串首尾没空格
s = removeExtraSpaces(s);
char[] chars = s.toCharArray();
// 先翻转整个字符串
// 例如:"hello world" -> "dlrow olleh"
reverse(chars, 0, chars.length - 1);
// 再反转每个单词
// 例如:"dlrow olleh" -> "world hello"
int start = 0; // removeExtraSpaces后保证第一个单词的开始下标一定是0
for (int i = 0; i <= chars.length; ++i) {
if (i == chars.length || chars[i] == ' ') { // 到达空格或者串尾,说明一个单词结束。进行翻转
reverse(chars, start, i - 1); // 翻转,注意是左闭右闭 []的翻转
start = i + 1; // 更新下一个单词的开始下标start
}
}
return new String(chars);
}
/**
* LeetCode 344. 反转字符串
* 使用异或运算交换字符,无需额外变量
*
* 异或运算特性:
* 1. a ^ a = 0
* 2. a ^ 0 = a
* 3. a ^ b ^ a = b
*
* 时间复杂度:O(n),其中n是需要反转的字符数量
* 空间复杂度:O(1),只需要常数级别的额外空间
*
* @param ch 需要反转的字符数组
* @param start 起始位置(包含)
* @param end 结束位置(包含)
*/
public static void reverseString(char[] ch, int start, int end) {
//异或法反转字符串,参照题目 344.反转字符串的解释
while (start < end) {
// 使用异或运算交换两个字符,无需额外变量
// 步骤1: ch[start] = ch[start] ^ ch[end]
ch[start] ^= ch[end];
// 步骤2: ch[end] = ch[start] ^ ch[end] = (ch[start] ^ ch[end]) ^ ch[end] = ch[start]
ch[end] ^= ch[start];
// 步骤3: ch[start] = ch[start] ^ ch[end] = (ch[start] ^ ch[end]) ^ ch[start] = ch[end]
ch[start] ^= ch[end];
// 移动指针
start++;
end--;
}
}
/**
* 主方法,用于测试各种字符串处理函数
*
* @param args 命令行参数
*/
public static void main(String[] args) {
// 测试示例1:翻转单词
String s1 = "the sky is blue";
String result1 = reverseWords(s1);
System.out.println("示例 1:");
System.out.println("输入: \"" + s1 + "\"");
System.out.println("输出: \"" + result1 + "\"");
// 测试示例2:翻转单词(带有多余空格)
String s2 = " hello world! ";
String result2 = reverseWords(s2);
System.out.println("\n示例 2:");
System.out.println("输入: \"" + s2 + "\"");
System.out.println("输出: \"" + result2 + "\"");
// 测试示例3:字符串左旋转(LeetCode 189的变种)
// 将字符串向左旋转n个位置
// 算法思路:先整体反转,再分别反转前n个和后面的字符
String s = "lljsdjnksdank";
int n = 2; // 左旋转的位置数
int len = s.length(); // 获取字符串长度
char[] chars = s.toCharArray();
reverseString(chars, 0, len - 1); // 反转整个字符串
reverseString(chars, 0, n - 1); // 反转前n个字符
reverseString(chars, n, len - 1); // 反转剩余的字符
System.out.println("\n示例 3:");
System.out.println("输入: \"" + s + "\", n = " + n);
System.out.println("输出: \"" + new String(chars) + "\"");
}
}