翻转字符串里的单词
力扣题目链接(opens new window)
给定一个字符串,逐个翻转字符串中的每个单词。
示例 1:
输入: “the sky is blue”
输出: “blue is sky the”
示例 2:
输入: “ hello world! “
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
思路
这道题目可以说是综合考察了字符串的多种操作。
一些同学会使用split库函数,分隔单词,然后定义一个新的string字符串,最后再把单词倒序相加,那么这道题题目就是一道水题了,失去了它的意义。
所以这里我还是提高一下本题的难度:不要使用辅助空间,空间复杂度要求为O(1)。
不能使用辅助空间之后,那么只能在原字符串上下功夫了。
想一下,我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒序了,那么再把单词反转一下,单词不就正过来了。
所以解题思路如下:
举个例子,源字符串为:”the sky is blue “
- 移除多余空格 : “the sky is blue”
- 字符串反转:”eulb si yks eht”
- 单词反转:”blue is sky the”
这样我们就完成了翻转字符串里的单词。
思路很明确了,我们说一说代码的实现细节,就拿移除多余空格来说,一些同学会上来写如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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本来就是O(n)的操作,erase实现原理题目:数组:就移除个元素很难么? (opens new window),最优的算法来移除元素也要O(n)。
erase操作上面还套了一个for循环,那么以上代码移除冗余空格的代码时间复杂度为O(n^2)。
那么使用双指针法来去移除空格,最后resize(重新设置)一下字符串的大小,就可以做到O(n)的时间复杂度。
如果对这个操作比较生疏了,可以再看一下这篇文章:数组:就移除个元素很难么? (opens new window)是如何移除元素的。
那么使用双指针来移除冗余空格代码如下: fastIndex走的快,slowIndex走的慢,最后slowIndex就标记着移除多余空格后新字符串的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void removeExtraSpaces(string& s) { int slowIndex = 0, fastIndex = 0; while (s.size() > 0 && fastIndex < s.size() && s[fastIndex] == ' ') { fastIndex++; } for (; fastIndex < s.size(); fastIndex++) { if (fastIndex - 1 > 0 && s[fastIndex - 1] == s[fastIndex] && s[fastIndex] == ' ') { continue; } else { s[slowIndex++] = s[fastIndex]; } } if (slowIndex - 1 > 0 && s[slowIndex - 1] == ' ') { s.resize(slowIndex - 1); } else { s.resize(slowIndex); } }
|
有的同学可能发现用erase来移除空格,在leetcode上性能也还行。主要是以下几点;:
- leetcode上的测试集里,字符串的长度不够长,如果足够长,性能差距会非常明显。
- leetcode的测程序耗时不是很准确的。
此时我们已经实现了removeExtraSpaces函数来移除冗余空格。
还做实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在344.反转字符串 (opens new window)和541.反转字符串II (opens new window)里已经讲过了。
代码如下:
1 2 3 4 5 6
| // 反转字符串s中左闭又闭的区间[start, end] void reverse(string& s, int start, int end) { for (int i = start, j = end; i < j; i++, j--) { swap(s[i], s[j]); } }
|
java代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class Solution { public String reverseWords(String s) { StringBuilder sb = removeEmpty(s); int start = 0; int end = s.length()-1; reverse(sb,start,end); reverseWord(sb); } public StringBuilder reverse(StringBuilder sb,int start,int end) { while(start<end) { char temp = sb.charAt(end); sb.setChar(start,temp); sb.setChar(end,temp); } } public void removeEmpty(String s) { StringBuilder sb = new StringBuilder(); start = 0; end = s.length()-1; while(s.charAt(start)==' ')start++; while(s.charAt(end)==' ')end--; while(start < end) { char tmp = s.charAt(start); if(tmp != ' ' || sb.charAt(sb.length()-1) != ' ') { sb.append(tmp); } } } public void reverseWord(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; } } }
|