(一)问题描述
. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/reverse-words-in-a-string/description/
给你一个字符串 s
,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s
中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s
中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
示例 1:
输入:s = "the sky is blue" " 输出:"blue is sky the"示例 2:
输入:s = " hello world " 输出:"world hello" 解释:反转后的字符串中不能存在前导空格和尾随空格。示例 3:
输入:s = "a good example" 输出:"example good a" 解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个
提示:
1 <= s.length <= 104
s
包含英文大小写字母、数字和空格' '
s
中 至少存在一个 单词
进阶:如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 O(1)
额外空间复杂度的 原地 解法。
(二)解决思路
整个问题可以拆解成三步:(1)去除所有重复的空格和首尾空格;(2)将所有单词反转,不考虑单词当中字母的顺序,也就是将整个字符串反转;(3)逐个反转每个单词中的字母。对于Java而言,String不能直接修改,因此不得不使用额外空间。如果是其他语言,这个方式是可以实现O(1)的空间复杂度的。使用双指针的方式也可以解决这个问题,但是在不使用额外空间的情况下不如这个三步的方式好理解。掌握好理解的方法考试时才更容易做得上嘛。
//处理首尾空格和重复的空格
public StringBuffer removeSpace(String s){
StringBuffer reverse=new StringBuffer();
int start=0,end=s.length()-1;
//跳过首尾空格
while(s.charAt(start)==' '){start++;}
while(s.charAt(end)==' '){end--;}
//注意这里是小于等于,不是小于,否则会漏掉最后一个字母
while(start<=end){
if(s.charAt(start)!=' '||reverse.charAt(reverse.length()-1)!=' '){
reverse.append(s.charAt(start));
}
start++;
}
return reverse;
}
//反转字符串,或字符串的一部分(从start到end)
public void reverseString(StringBuffer s,int start,int end){
char temp;
while(start<end){
temp=s.charAt(start);
s.setCharAt(start,s.charAt(end));
s.setCharAt(end,temp);
start++;
end--;
}
}
//反转其中的每个单词,找到单词,然后调用反转字符串函数
public void reversEachWord(StringBuffer result){
int start=0,end=0;
while(start<result.length()){
while(end<result.length()&&result.charAt(end)!=' '){
end++;
}
reverseString(result,start,end-1);
start=end+1;
end=start+1;
}
}
//leetcode里给好的那个要填答案的函数
public String reverseWords(String s) {
//去除多余的空格
StringBuffer result=removeSpace(s);
//反转整个字符串
reverseString(result,0,result.length()-1);
//反转单个单词
reversEachWord(result);
return result.toString();
}
(三)易错点
总结一下我认为比较容易写晕的几个地方:
1. 空格也是个字符啊,空格不是null,别拿null判断!
2. 在Java当中,引用类型变量作为外部参数传入函数中时,它的值是会被函数操作修改的,而基本类型不会。所有对象都是引用类型变量,除了八种基本数据类型以外都是引用类型(八中基本数据类型包括整型(byte、short、int、long)浮点型(float、double)字符型(char)布尔型(boolean)),这就是为什么两个反转函数可以这么写。
3. StringBuffer有一个reverse方法,在反转整个字符串这一步用这个方法也是可以的。在反转单个单词这里,额外使用一个反转部分字符串的函数更好,直接写的话在交换部分可能会涉及start和end的移动,移着移着乱套了。
4. 我写乱了的地方:
- 怎么删掉头尾的空格,还能删掉重复的空格并且保留一个。脑子晕了,使用额外空间的情况下这个操作太简单了:放入额外空间之前直接跳过头尾空格元素,中间的空格就直接判断一下上一个添加进去的是不是空格,是的话就跳过。如果是Java外的其他语言,应当使用和27.移除元素思路类似的快慢指针来解决。
- 怎么找到每个单词,并且每次只反转这个单词。我最开始想的是最后一个单词应该是end先接触字符串的末端,应该用end是否超过字符串长度来控制最外层的循环。实际上end每次要去找单词的结尾,在找到最后一个结尾时操作其实还没有结束,所以应该用start来控制最外层循环,当start超过字符串长度时再终止循环。每次操作结束后,start应该位于上一次end的后一个字母,end应该要调整到start后一位。我忘记调整end,导致死循环。另外,每次反转单词时,end是位于单词末尾后的那个空格上,因此传入参数是end-1。