题目:https://leetcode-cn.com/problems/reverse-words-in-a-string/
自己的思路:使用两个指针,i 指针负责从后向前遍历整个字符串,j 指针找到单词的末尾字符,单词开头的特点是:空格+字符(字符串的开头添加一个空格);单词末尾的特点:字符+空格/字符;
JAVA:
public static String reverseWords(String s) { StringBuilder str = new StringBuilder(); s = " "+s;//字符串的开头添加一个空格 for (int i = s.length() - 1, j = s.length() - 1; i >= 1; i--){ if (s.charAt(i - 1) != ' ' && s.charAt(i) == ' '){//如果 i 所指位置为空,前一位不为空,则说明前一位为单词的结尾,将 j 指向此处; j = i-1; } if (s.charAt(i - 1) == ' ' && s.charAt(i) != ' ') {//如果 i 所指位置不为空,前一位为空,则说明找到了该单词的开头,截取头和尾之间的字符串即为一个单词,并在单词后添加一个空格。 String temp = s.substring(i, j+1); str.append(temp+" "); } } str.deleteCharAt(str.length()-1);//去除末尾多余的一个空格 return str.toString(); }
知识点:
delete方法与deleteCharAt两个方法都是用来删除StringBuffer字符串指定索引字符的方法,
java.lang.StringBuffer.delete(int a,int b)有两个参数,使用时删除索引从a开始(包含a)到b(不包含b)的所有字符;
java.lang.StringBuffer.deleteCharAt(int a)只有一个参数,使用时删除索引为a的字符;
效果:
官方:https://leetcode-cn.com/problems/reverse-words-in-a-string/solution/fan-zhuan-zi-fu-chuan-li-de-dan-ci-by-leetcode-sol/
方法一:使用语言特性
很多语言对字符串提供了 split(拆分),reverse(翻转)和 join(连接)等方法,因此我们可以简单的调用内置的 API 完成操作:
- 使用 split 将字符串按空格分割成字符串数组;
- 使用 reverse 将字符串数组进行反转;
- 使用 join 方法将字符串数组拼成一个字符串。
JAVA:
public String reverseWords(String s) { // 除去开头和末尾的空白字符 s = s.trim(); // 正则匹配连续的空白字符作为分隔符分割 List<String> wordList = Arrays.asList(s.split("\s+")); Collections.reverse(wordList); return String.join(" ", wordList); }
方法二:自行编写对应的函数
我们也可以不使用语言中的 API,而是自己编写对应的函数。在不同语言中,这些函数实现是不一样的,主要的差别是有些语言的字符串不可变(如 Java 和 Python),有些语言的字符串可变(如 C++)。
对于字符串不可变的语言,首先得把字符串转化成其他可变的数据结构,同时还需要在转化的过程中去除空格。
对于字符串可变的语言,就不需要再额外开辟空间了,直接在字符串上原地实现。在这种情况下,反转字符和去除空格可以一起完成。
JAVA: public String reverseWords(String s) { StringBuilder sb = trimSpaces(s); // 翻转字符串 reverse(sb, 0, sb.length() - 1); // 翻转每个单词 reverseEachWord(sb); return sb.toString(); } public StringBuilder trimSpaces(String s) { int left = 0, right = s.length() - 1; // 去掉字符串开头的空白字符 while (left <= right && s.charAt(left) == ' ') { ++left; } // 去掉字符串末尾的空白字符 while (left <= right && s.charAt(right) == ' ') { --right; } // 将字符串间多余的空白字符去除 StringBuilder sb = new StringBuilder(); while (left <= right) { char c = s.charAt(left); if (c != ' ') { sb.append(c); } else if (sb.charAt(sb.length() - 1) != ' ') { sb.append(c); } ++left; } return sb; } public void reverse(StringBuilder sb, int left, int right) { while (left < right) { char tmp = sb.charAt(left);
//public void setCharAt(int index, char ch) 很简单,把第index位的字符替换成ch sb.setCharAt(left++, sb.charAt(right)); sb.setCharAt(right--, tmp); } } public void reverseEachWord(StringBuilder sb) { int n = sb.length(); int start = 0, end = 0; while (start < n) { // 循环至单词的末尾 while (end < n && sb.charAt(end) != ' ') { ++end; } // 翻转单词 reverse(sb, start, end - 1); // 更新start,去找下一个单词 start = end + 1; ++end; } }
C++: 别人写的
https://programmercarl.com/0151.%E7%BF%BB%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%87%8C%E7%9A%84%E5%8D%95%E8%AF%8D.html#%E5%85%B6%E4%BB%96%E8%AF%AD%E8%A8%80%E7%89%88%E6%9C%AC
class Solution { public: // 反转字符串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]); } } // 移除冗余空格:使用双指针(快慢指针法)O(n)的算法 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); // 重新设置字符串大小 } } string reverseWords(string s) { removeExtraSpaces(s); // 去掉冗余空格 reverse(s, 0, s.size() - 1); // 将字符串全部反转 int start = 0; // 反转的单词在字符串里起始位置 int end = 0; // 反转的单词在字符串里终止位置 bool entry = false; // 标记枚举字符串的过程中是否已经进入了单词区间 for (int i = 0; i < s.size(); i++) { // 开始反转单词 if (!entry) { start = i; // 确定单词起始位置 entry = true; // 进入单词区间 } // 单词后面有空格的情况,空格就是分词符 if (entry && s[i] == ' ' && s[i - 1] != ' ') { end = i - 1; // 确定单词终止位置 entry = false; // 结束单词区间 reverse(s, start, end); } // 最后一个结尾单词之后没有空格的情况 if (entry && (i == (s.size() - 1)) && s[i] != ' ' ) { end = i;// 确定单词终止位置 entry = false; // 结束单词区间 reverse(s, start, end); } } return s; } // 当然这里的主函数reverseWords写的有一些冗余的,可以精简一些,精简之后的主函数为: /* 主函数简单写法 string reverseWords(string s) { removeExtraSpaces(s); reverse(s, 0, s.size() - 1); for(int i = 0; i < s.size(); i++) { int j = i; // 查找单词间的空格,翻转单词 while(j < s.size() && s[j] != ' ') j++; reverse(s, i, j - 1); i = j; } return s; } */ };
方法三:双端队列
由于双端队列支持从队列头部插入的方法,因此我们可以沿着字符串一个一个单词处理,然后将单词压入队列的头部,再将队列转成字符串即可。
JAVA双端队列:https://www.cnblogs.com/walxt/p/12442506.html
JAVA: public String reverseWords(String s) { int left = 0, right = s.length() - 1; // 去掉字符串开头的空白字符 while (left <= right && s.charAt(left) == ' ') { ++left; } // 去掉字符串末尾的空白字符 while (left <= right && s.charAt(right) == ' ') { --right; } Deque<String> d = new ArrayDeque<String>(); StringBuilder word = new StringBuilder(); while (left <= right) { char c = s.charAt(left); if ((word.length() != 0) && (c == ' ')) { // 将单词 push 到队列的头部 d.offerFirst(word.toString()); word.setLength(0); } else if (c != ' ') { word.append(c); } ++left; } d.offerFirst(word.toString()); return String.join(" ", d); }
C++: class Solution { public: string reverseWords(string s) { int left = 0, right = s.size() - 1; // 去掉字符串开头的空白字符 while (left <= right && s[left] == ' ') ++left; // 去掉字符串末尾的空白字符 while (left <= right && s[right] == ' ') --right; deque<string> d; string word; while (left <= right) { char c = s[left]; if (word.size() && c == ' ') { // 将单词 push 到队列的头部 d.push_front(move(word)); word = ""; } else if (c != ' ') { word += c; } ++left; } d.push_front(move(word)); string ans; while (!d.empty()) { ans += d.front(); d.pop_front(); if (!d.empty()) ans += ' '; } return ans; } };