题目1 :151.翻转字符串里的单词
题目要求:不要使用辅助空间,空间复杂度要求为O(1)
解题思路:
1.移除多余空格
2.将整个字符串反转
3.将每个单词反转
移除多余空格:
void removeExtraSpaces(string& s) {
for (int i = s.size() - 1; i > 0; i--) {
if (s[i] == s[i - 1] && s[i] == ' ') {
s.erase(s.begin() + i);
}
}
// 删除字符串最后面的空格
if (s.size() > 0 && s[s.size() - 1] == ' ') {
s.erase(s.begin() + s.size() - 1);
}
// 删除字符串最前面的空格
if (s.size() > 0 && s[0] == ' ') {
s.erase(s.begin());
}
}
逻辑很简单,从前向后遍历,遇到空格了就erase。
但一个erase本来就是O(n)的操作。
erase操作上面还套了一个for循环,那么以上代码移除冗余空格的代码复杂度为O(n^2)。
那么如果使用双指针法来移除空格,最后resize(重新设置)一下字符串的大小,就可以做到O(n)的时间复杂度。
class Solution {
/**
* 不使用Java内置方法实现
* <p>
* 1.去除首尾以及中间多余空格
* 2.反转整个字符串
* 3.反转各个单词
*/
public String reverseWords(String s) {
// System.out.println("ReverseWords.reverseWords2() called with: s = [" + s + "]");
// 1.去除首尾以及中间多余空格
StringBuilder sb = removeSpace(s);
// 2.反转整个字符串
reverseString(sb, 0, sb.length() - 1);
// 3.反转各个单词
reverseEachWord(sb);
return sb.toString();
}
private StringBuilder removeSpace(String s) {
// System.out.println("ReverseWords.removeSpace() called with: s = [" + 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++;
}
// System.out.println("ReverseWords.removeSpace returned: sb = [" + sb + "]");
return sb;
}
/**
* 反转字符串指定区间[start, end]的字符
*/
public void reverseString(StringBuilder sb, int start, int end) {
// System.out.println("ReverseWords.reverseString() called with: sb = [" + sb + "], start = [" + start + "], end = [" + end + "]");
while (start < end) {
char temp = sb.charAt(start);
sb.setCharAt(start, sb.charAt(end));
sb.setCharAt(end, temp);
start++;
end--;
}
// System.out.println("ReverseWords.reverseString returned: sb = [" + sb + "]");
}
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;
}
}
}
1. 去除首尾和中间多余空格
-
使用 removeSpace 方法。
-
这一步生成一个清理后的 StringBuilder。
-
实现细节:
-
确定有效的起始和结束索引:
start向右移动,直到遇到非空格字符
end向左移动,直到遇到非空格字符 -
遍历start 到end ,构造新的字符串:
如果当前字符不是空格,直接加入StringBuilder
如果当前字符是空格,则检查上一个字符是否为非空格,避免连续空格。
-
int start = 0;
int end = s.length() - 1;
while (s.charAt(start) == ' ') start++; // 去掉左侧空格
while (s.charAt(end) == ' ') end--; // 去掉右侧空格
遍历start 到end :
StringBuilder sb = new StringBuilder();
while (start <= end) {
char c = s.charAt(start);//获取当前字符
if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
sb.append(c);
}
start++;
}
if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
sb.append(c);
}
该语句的条件含义:
-
c ! = ’ ’
- 当前字符c不是空格,直接允许加入StringBuilder
- 因为非空字符永远都是需要保留的
- sb.charAt(sb.length() - 1) != ’ ’
- 当前字符c是空格时,检查已经加入StringBuilder中的最后一个字符
- 如果最后一个字符 不是空格 (sb.charAt(sb.length()-1) != ’ '),说明之前的字符是有效字符,当前的空格可以被加入。
- 如果最后一个字符已经是空格,则跳过当前空格,防止连续空格出现
两者用 逻辑或 (||) 连接:
- 如果 当前字符是非空格 (c != ’ '),无需检查 sb,直接加入。
- 如果 当前字符是空格,则必须检查 StringBuilder
的最后一个字符是否也是空格。
2. 反转整个字符串
/**
* 反转字符串指定区间[start, end]的字符
*/
public void reverseString(StringBuilder sb, int start, int end) {
// System.out.println("ReverseWords.reverseString() called with: sb = [" + sb + "], start = [" + start + "], end = [" + end + "]");
while (start < end) {
char temp = sb.charAt(start);
sb.setCharAt(start, sb.charAt(end));
sb.setCharAt(end, temp);
start++;
end--;
}
// System.out.println("ReverseWords.reverseString returned: sb = [" + sb + "]");
}
// 2.反转整个字符串
reverseString(sb, 0, sb.length() - 1);//传入参数
3.reverseEachWord方法
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;
}
}
-
使用 reverseString 方法。
将字符串的某个区间 [start, end] 内的字符顺序反转。
实现细节:
使用双指针:左指针 start 和右指针 end。
交换两端字符,然后向中间推进,直到 start >= end。 -
反转每个单词
-
使用 reverseEachWord 方法。
在已经整体反转的字符串基础上,逐一反转每个单词。
实现细节:- 使用两个指针:
- start: 表示当前单词的起始位置。
- end: 从 start 开始寻找单词的末尾(遇到空格或字符串末尾时停止)。
- 找到单词的区间 [start, end-1],调用 reverseString 方法反转单词。
- 更新 start 和end,继续处理下一个单词。
●卡码网:55.右旋转字符串
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = Integer.parseInt(in.nextLine());
String s = in.nextLine();
int len = s.length(); //获取字符串长度
char[] chars = s.toCharArray();
reverseString(chars, 0, len - 1); //反转整个字符串
reverseString(chars, 0, n - 1); //反转前一段字符串,此时的字符串首尾尾是0,n - 1
reverseString(chars, n, len - 1); //反转后一段字符串,此时的字符串首尾尾是n,len - 1
System.out.println(chars);
}
public static void reverseString(char[] ch, int start, int end) {
//异或法反转字符串,参照题目 344.反转字符串的解释
while (start < end) {
ch[start] ^= ch[end];
ch[end] ^= ch[start];
ch[start] ^= ch[end];
start++;
end--;
}
}
}
abcdefg---------(整体反转)---------> gf edcba ------- (局部反转)------>fg abcde
将字符串分成两部分:
-
前部分(索引[0,n-1])
-
后部分(索引[n,len - 1])
通过以下步骤实现: -
反转整个字符串
-
再反转前一部分
-
再反转后一部分
-
输出完成分段反转后的字符串
代码解析
- 输入读取:
Scanner in = new Scanner(System.in);
int n = Integer.parseInt(in.nextLine());
String s = in.nextLine();
使用 Scanner 获取用户输入。
读取整数 n(分割点),以及字符串 s。
- 字符串长度和字符数组:
int len = s.length(); // 获取字符串长度
char[] chars = s.toCharArray(); // 将字符串转换为字符数组
将字符串转为字符数组方便处理,因为 Java 中字符串是不可变的,而字符数组可以直接修改。
- 字符串分段反转
reverseString(chars, 0, len - 1); // 反转整个字符串
reverseString(chars, 0, n - 1); // 反转前一段
reverseString(chars, n, len - 1); // 反转后一段
- 第一步: 反转整个字符串,形成一个基准的倒序排列。
- 第二步: 反转前半部分,使其回到原始顺序。
- 第三步: 反转后半部分,使其回到原始顺序。
- 输出结果:
System.out.println(chars);
//将处理后的字符数组转化为字符串输出。
- reverseString 方法
反转字符数组指定区间 [start, end] 的内容。
使用了异或交换的方式,原地进行反转,不需要额外空间。
while (start < end) {
ch[start] ^= ch[end]; // 异或交换第一步
ch[end] ^= ch[start]; // 异或交换第二步
ch[start] ^= ch[end]; // 异或交换第三步
start++; // 左指针右移
end--; // 右指针左移
}
●字符串总结
字符串是若干字符组成的有限序列,也可以理解为是一个字符数组
双指针法在数组,链表和字符串中很常用。
●双指针回顾