一、翻转字符串里的单词
1.题目描述
给定一个字符串,逐个翻转字符串中的每个单词。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
2.思路
我这里主要看的快慢指针的思路
- 先移除字符串中多余的空格(首尾空格和单词间多余空格)
- 将整个整个字符串反转
- 最后把每个单词逐个反转回来
主方法 reverseWords
public String reverseWords(String s) {
char[] chars = s.toCharArray(); // 将字符串转为字符数组,方便操作
chars = removeExtraSpaces(chars); // 1.去除多余空格
reverse(chars, 0, chars.length - 1); // 2.整个字符串反转
reverseEachWord(chars); // 3.单词反转
return new String(chars); // 将字符数组转回字符串并返回
}
第一步:removeExtraSpaces - 去除多余空格
- 字符串开头的空格
- 字符串结尾的空格
- 单词之间的多个空格
public char[] removeExtraSpaces(char[] chars) {
int slow = 0; // 慢指针:负责构建"没有多余空格"的新字符串
for (int fast = 0; fast < chars.length; fast++) { // 快指针:负责遍历原字符串
// 核心逻辑:只处理"非空格"字符,跳过所有空格
if (chars[fast] != ' ') {
// 重要:如果不是第一个单词,在单词前加一个空格
if (slow != 0)
chars[slow++] = ' ';
// 把整个单词的字符,从fast位置复制到slow位置
while (fast < chars.length && chars[fast] != ' ') {
chars[slow++] = chars[fast++];
}
}
// 如果是空格,fast会自动++,相当于跳过空格
}
// 创建新的字符数组,长度为慢指针的位置(有效字符的长度)
char[] newChars = new char[slow];
System.arraycopy(chars, 0, newChars, 0, slow);
//System.arraycopy(源数组, 源数组开始复制的位置, 目标数组, 目标数组开始粘贴的位置, 需要复制的长度);
return newChars;
}
- 使用快慢指针技术,快指针负责遍历原字符串,慢指针负责构建新的无空格字符串
- 遇到非空格字符时,先判断是否需要加空格(不是第一个单词的话)
- 然后复制整个单词
- 最后返回刚好合适长度的新字符数组
举例:
输入 " hello world! ",处理后变成 "hello world!"
第二步:reverse - 反转指定范围的字符
public void reverse(char[] chars, int left, int right) {
if (right >= chars.length) {
System.out.println("set a wrong right");
return;
}
// 左右指针向中间移动并交换字符
while (left < right) {
// 交换两个字符(使用异或运算,也可以用临时变量)
chars[left] ^= chars[right];
chars[right] ^= chars[left];
chars[left] ^= chars[right];
left++;
right--;
}
}
- 接收一个字符数组和两个索引(left 和 right)
- 交换 left 和 right 位置的字符,然后 left 右移,right 左移
- 直到 left >= right 时停止
举例:
对 "hello world" 整个字符串反转(left=0, right=10)会得到 "dlrow olleh"
第三步:reverseEachWord - 反转每个单词
public void reverseEachWord(char[] chars) {
int start = 0; // 记录每个单词的起始位置
// 遍历字符数组,end是每个单词的结束位置+1
for (int end = 0; end <= chars.length; end++) {
// 当遇到空格或到达数组末尾时,说明找到一个完整单词
if (end == chars.length || chars[end] == ' ') {
reverse(chars, start, end - 1); // 反转从start到end-1的单词
start = end + 1; // 更新下一个单词的起始位置
}
}
}
- 遍历字符数组,寻找单词的边界(空格或数组末尾)
- 找到一个单词后,调用 reverse 方法反转这个单词
- 更新 start 指针到下一个单词的起始位置
3.代码
class Solution {
//用 char[] 来实现 String 的 removeExtraSpaces,reverse 操作
public String reverseWords(String s) {
char[] chars = s.toCharArray();
//1.去除首尾以及中间多余空格
chars = removeExtraSpaces(chars);
//2.整个字符串反转
reverse(chars, 0, chars.length - 1);
//3.单词反转
reverseEachWord(chars);
return new String(chars);
}
//1.用 快慢指针 去除首尾以及中间多余空格,可参考数组元素移除的题解
public char[] removeExtraSpaces(char[] chars) {
int slow = 0;
for (int fast = 0; fast < chars.length; fast++) {
//先用 fast 移除所有空格
if (chars[fast] != ' ') {
//在用 slow 加空格。 除第一个单词外,单词末尾要加空格
if (slow != 0)
chars[slow++] = ' ';
//fast 遇到空格或遍历到字符串末尾,就证明遍历完一个单词了
while (fast < chars.length && chars[fast] != ' ')
chars[slow++] = chars[fast++];
}
}
//相当于 c++ 里的 resize()
char[] newChars = new char[slow];
System.arraycopy(chars, 0, newChars, 0, slow);
return newChars;
}
//双指针实现指定范围内字符串反转,可参考字符串反转题解
public void reverse(char[] chars, int left, int right) {
if (right >= chars.length) {
System.out.println("set a wrong right");
return;
}
while (left < right) {
chars[left] ^= chars[right];
chars[right] ^= chars[left];
chars[left] ^= chars[right];
left++;
right--;
}
}
//3.单词反转
public void reverseEachWord(char[] chars) {
int start = 0;
//end <= s.length() 这里的 = ,是为了让 end 永远指向单词末尾后一个位置,这样 reverse 的实参更好设置
for (int end = 0; end <= chars.length; end++) {
// end 每次到单词末尾后的空格或串尾,开始反转单词
if (end == chars.length || chars[end] == ' ') {
reverse(chars, start, end - 1);
start = end + 1;
}
}
}
}
二、右旋字符串
1.题目描述
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。
输入:输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。
输出:输出共一行,为进行了右旋转操作后的字符串。
样例输入:
2
abcdefg
样例输出:
fgabcde
数据范围:1 <= k < 10000, 1 <= s.length < 10000;
2.思路
1.反转所有字符串
2.反转前k个字符串
3.反转后四个字符串
3.代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int k = sc.nextInt();
String s = sc.next();
int count = 0;
char[] chars = s.toCharArray();
reverse(chars,0,chars.length-1);
reverse(chars,0,k-1);
reverse(chars,k,chars.length-1);
System.out.println(chars);
}
public static void reverse(char[] ch, int i, int i1) {
while(i<i1){
ch[i]^=ch[i1];
ch[i1]^=ch[i];
ch[i]^=ch[i1];
i++;
i1--;
}
}
}
三、字符串总结篇
1.字符串基础
字符串是由字符组成的序列,用于表示文本等数据,在编程中是基础且常用的数据类型。
2.库函数使用
首先我们初学算法最好不要用函数,搞清楚原理。
若为快速实现功能、提高效率,库函数(如字符串的拼接、查找等方法)很便捷;若为学习算法逻辑,可手动实现相关操作。
3.双指针法
是处理字符串的常用技巧,可用于字符串的反转(如左右指针向中间移动交换字符)、查找等场景,能高效解决问题。
4.反转系列操作
-
反转字符串:将整个字符串的字符顺序颠倒。
-
反转字符串 Ⅱ:按特定规则(如每 2k 个字符,反转前 k 个)进行局部反转。
-
翻转字符串里的单词:先反转整个字符串,再反转每个单词,可实现单词顺序翻转且单词内部字符顺序正确。
-
右旋转字符串:通过三次反转(整体反转、前部分反转、后部分反转)等方式,将字符串右旋转指定长度。
四、双指针总结篇
1.字符串篇
-
反转字符串:定义首尾双指针,向中间移动并交换字符,实现原地反转,时间复杂度 O(n)。
-
替换空格:先扩充字符串至替换后大小,再用双指针从后向前替换,避免从前向后的 O(n2) 复杂度,提升效率。
-
删除冗余空格:双指针可高效完成,避免使用
erase
带来的高复杂度,将操作优化至 O(n)。
2.链表篇
-
翻转链表:双指针改变节点
next
指向,实现链表反转,无需新建链表,考验对链表和指针的熟悉度。 -
找链表环及入口:快慢双指针,快指针每次移两步、慢指针移一步,相遇则有环;再通过数学推理,结合双指针可找到环入口。
3.N 数之和篇
-
两数之和:若求数值,双指针可行;但因需下标,更适合哈希法。
-
三数之和:双指针将暴力 O(n3) 降为 O(n2),通过前后指针向中间逼近,在一层循环内完成类似两层循环的工作,还能高效去重。
-
四数及更多数之和:在三数之和基础上嵌套循环,双指针依次将暴力 O(n4) 等复杂度降低,原理与三数之和一致,可扩展到 N 数之和。
五、个人心得(真实)
在今天学习讲字符串学习完毕了,由于是新手看到官方文档跳过了kmp俩道题目学习,希望二刷时候可以很好的理解掌握,目前状态还不错嘻嘻,感觉今天俩道题目的思路都和我自己不一样,开拓了思路,更加简单的思路让我可学到咯嘻嘻嘻!!!