本节内容
344.反转字符串
541. 反转字符串II
剑指Offer 05.替换空格
151.翻转字符串里的单词
剑指Offer58-II.左旋转字符串
344.反转字符串※ 建议 : 本题是字符串基础题目,就是考察 reverse 函数的实现,同时也明确一下 平时刷题什么时候用 库函数,什么时候 不用库函数
题目链接: https://leetcode.cn/problems/reverse-string/ 文章讲解: https://programmercarl.com/0344.%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.html
https://www.yuanql.top/2023/06/14/02_leetcode/344.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2/
题目分析
方法一:双指针,对撞指针,一个指向前面,一个指向最后面,然后交换双方的数值,直到全部交换完毕。 结束条件 left >= right。
方案一 1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public void reverseString (char [] s) { int left = 0 , right = s.length - 1 ; char temp; while (left <= right) { temp = s[left]; s[left++] = s[right]; s[right--] = temp; } } }
结果 解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:49.5 MB,击败了31.02% 的Java用户
分析 时间复杂度: O( n )
空间复杂度: O( 1 )
代码随想录 https://programmercarl.com/0344.%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.html
思想基本与方案一一致。
对于字符串,我们定义两个指针(也可以说是索引下标),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。
以字符串hello
为例,过程如下:
代码实现 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 class Solution { public void reverseString (char [] s) { int l = 0 ; int r = s.length - 1 ; while (l < r) { s[l] ^= s[r]; s[r] ^= s[l]; s[l] ^= s[r]; l++; r--; } } }class Solution { public void reverseString (char [] s) { int l = 0 ; int r = s.length - 1 ; while (l < r){ char temp = s[l]; s[l] = s[r]; s[r] = temp; l++; r--; } } }
541. 反转字符串II※ 建议 :本题又进阶了,自己先去独立做一做,然后在看题解,对代码技巧会有很深的体会。
题目链接: https://leetcode.cn/problems/reverse-string-ii/ 文章讲解: https://programmercarl.com/0541.%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2II.html
https://www.yuanql.top/2023/06/14/02_leetcode/541.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%20II/
题目分析
分段进行处理,先将有规律的进行处理,一段一段进行 最后剩余不足的内容进行特殊处理,以此方法来解答此问题。
方案一 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 class Solution { public String reverseStr (String s, int k) { int length = s.length(), left, right, index = 0 ; char temp; StringBuilder result = new StringBuilder (s); for (; (2 * k * (index + 1 )) < length; index++) { left = 2 * k * index; right = 2 * k * index + k - 1 ; reverse(left, right, result); } if ((2 * k * index + k) < length) { left = 2 * k * index; right = 2 * k * index + k - 1 ; reverse(left, right, result); } else { left = 2 * k * index; right = length - 1 ; reverse(left, right, result); } return result.toString(); } private void reverse (int left, int right, StringBuilder result) { char temp; while (left < right) { temp = result.charAt(left); result.setCharAt(left, result.charAt(right)); result.setCharAt(right, temp); left++; right--; } } }
结果 解答成功: 执行耗时:1 ms,击败了69.91% 的Java用户 内存消耗:42.3 MB,击败了34.14% 的Java用户
分析 时间复杂度: O( n )
空间复杂度: O( n )
改进 突然发现本次编写的算法还不如以前在写的方法简单: https://www.yuanql.top/2023/06/14/02_leetcode/541.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%20II/
人生是螺旋向前的
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 class Solution { public String reverseStr (String s, int k) { char [] sChar = s.toCharArray(); int n = sChar.length, left = 0 ; do { if ((left + k) >= n) { func(sChar, left, n - 1 ); } else { func(sChar, left, left + k -1 ); } left = left + 2 * k; } while ( left < n ); return new String (sChar); } public void func (char [] s, int left, int right) { while (left < right) { char temp = s[left]; s[left] = s[right]; s[right] = temp; left++; right--; } } }
代码随想录 https://programmercarl.com/0541.%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2II.html
一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。
其实在遍历字符串的过程中,只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。
因为要找的也就是每2 * k 区间的起点,这样写,程序会高效很多。
所以当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。
代码实现 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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 class Solution { public String reverseStr (String s, int k) { StringBuffer res = new StringBuffer (); int length = s.length(); int start = 0 ; while (start < length) { StringBuffer temp = new StringBuffer (); int firstK = (start + k > length) ? length : start + k; int secondK = (start + (2 * k) > length) ? length : start + (2 * k); temp.append(s.substring(start, firstK)); res.append(temp.reverse()); if (firstK < secondK) { res.append(s.substring(firstK, secondK)); } start += (2 * k); } return res.toString(); } }class Solution { public String reverseStr (String s, int k) { char [] ch = s.toCharArray(); for (int i = 0 ; i < ch.length; i += 2 * k){ int start = i; int end = Math.min(ch.length - 1 , start + k - 1 ); while (start < end){ ch[start] ^= ch[end]; ch[end] ^= ch[start]; ch[start] ^= ch[end]; start++; end--; } } return new String (ch); } }class Solution { public String reverseStr (String s, int k) { char [] ch = s.toCharArray(); for (int i = 0 ;i < ch.length;i += 2 * k){ int start = i; int end = Math.min(ch.length - 1 ,start + k - 1 ); while (start < end){ char temp = ch[start]; ch[start] = ch[end]; ch[end] = temp; start++; end--; } } return new String (ch); } }
剑指Offer 05.替换空格※ 建议 :对于线性数据结构,填充或者删除,后序处理会高效的多。好好体会一下。
题目链接: https://leetcode.cn/problems/ti-huan-kong-ge-lcof/ 文章讲解: https://programmercarl.com/%E5%89%91%E6%8C%87Offer05.%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC.html
https://www.yuanql.top/2023/06/14/02_leetcode/%E5%89%91%E6%8C%87%20Offer%2005.%20%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC/
题目分析
方案一 1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public String replaceSpace (String s) { StringBuilder result = new StringBuilder (); for (int i = 0 ; i < s.length(); i++) { if (s.charAt(i) == ' ' ) { result.append("%20" ); } else { result.append(s.charAt(i)); } } return result.toString(); } }
结果 解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:39.5 MB,击败了40.78% 的Java用户
分析 时间复杂度: O( n )
空间复杂度: O( n )
代码随想录 https://programmercarl.com/%E5%89%91%E6%8C%87Offer05.%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC.html
如果想把这道题目做到极致,就不要只用额外的辅助空间了!
首先扩充数组到每个空格替换成”%20”之后的大小。 (分析了一下其代码,对应java而言,其并不是一个好方法,因为对于java而言,其String 是 final的,如果对其进行修改的话,其都要重新new一个String对象)
然后从后向前替换空格,也就是双指针法,过程如下:
i指向新长度的末尾,j指向旧长度的末尾。
有同学问了,为什么要从后向前填充,从前向后填充不行么?
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。
其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这么做有两个好处:
不用申请新数组。
从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
代码实现 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 46 47 48 49 50 51 52 53 54 public static String replaceSpace (String s) { if (s == null ) { return null ; } StringBuilder sb = new StringBuilder (); for (int i = 0 ; i < s.length(); i++) { if (s.charAt(i) == ' ' ) { sb.append("%20" ); } else { sb.append(s.charAt(i)); } } return sb.toString(); }public String replaceSpace (String s) { if (s == null || s.length() == 0 ){ return s; } StringBuilder str = new StringBuilder (); for (int i = 0 ; i < s.length(); i++) { if (s.charAt(i) == ' ' ){ str.append(" " ); } } if (str.length() == 0 ){ return s; } int left = s.length() - 1 ; s += str.toString(); int right = s.length()-1 ; char [] chars = s.toCharArray(); while (left>=0 ){ if (chars[left] == ' ' ){ chars[right--] = '0' ; chars[right--] = '2' ; chars[right] = '%' ; }else { chars[right] = chars[left]; } left--; right--; } return new String (chars); }
151.翻转字符串里的单词※ 建议 :这道题目基本把 刚刚做过的字符串操作 都覆盖了,不过就算知道解题思路,本题代码并不容易写,要多练一练。
题目链接: https://leetcode.cn/problems/reverse-words-in-a-string/ 文章讲解: 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
https://www.yuanql.top/2023/06/14/02_leetcode/151.%20%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8D%95%E8%AF%8D/
题目分析
将字符串 s 从后向前遍历,设置一个标志位来确认空格与非空格的转换时机。 因为是倒叙进行遍历的,所以得到数据之后要对其进行反转操作,在将其放入到result中。
方案一 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 class Solution { public String reverseWords (String s) { StringBuilder result = new StringBuilder (), temp = new StringBuilder (); boolean flag = false ; for (int i = s.length() - 1 ; i >= 0 ; i--) { if (s.charAt(i) == ' ' ) { if (flag) { result.append(temp.reverse()); result.append(' ' ); temp = new StringBuilder (); } flag = false ; } else { flag = true ; temp.append(s.charAt(i)); } } if (flag) { result.append(temp.reverse()); temp = new StringBuilder (); } else { result.deleteCharAt(result.length() - 1 ); } return result.toString(); } }
结果 解答成功: 执行耗时:4 ms,击败了80.99% 的Java用户 内存消耗:40.9 MB,击败了67.44% 的Java用户
分析 时间复杂度: O( n )
空间复杂度: O( n )
代码随想录 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
这道题目可以说是综合考察了字符串的多种操作。
一些同学会使用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 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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 class Solution { public String reverseWords (String s) { StringBuilder sb = removeSpace(s); reverseString(sb, 0 , sb.length() - 1 ); reverseEachWord(sb); return sb.toString(); } private StringBuilder removeSpace (String 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++; } return sb; } public void reverseString (StringBuilder sb, int start, int end) { while (start < end) { char temp = sb.charAt(start); sb.setCharAt(start, sb.charAt(end)); sb.setCharAt(end, temp); start++; end--; } } 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 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 class Solution { public String reverseWords (String s) { char [] initialArr = s.toCharArray(); char [] newArr = new char [initialArr.length+1 ]; int newArrPos = 0 ; int i = initialArr.length-1 ; while (i>=0 ){ while (i>=0 && initialArr[i] == ' ' ){i--;} int right = i; while (i>=0 && initialArr[i] != ' ' ){i--;} for (int j = i+1 ; j <= right; j++) { newArr[newArrPos++] = initialArr[j]; if (j == right){ newArr[newArrPos++] = ' ' ; } } } if (newArrPos == 0 ){ return "" ; }else { return new String (newArr,0 ,newArrPos-1 ); } } }
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 46 47 48 49 50 51 class Solution { public String reverseWords (String s) { char [] initialArr = s.toCharArray(); reverse(initialArr, 0 , s.length() - 1 ); int k = 0 ; for (int i = 0 ; i < initialArr.length; i++) { if (initialArr[i] == ' ' ) { continue ; } int tempCur = i; while (i < initialArr.length && initialArr[i] != ' ' ) { i++; } for (int j = tempCur; j < i; j++) { if (j == tempCur) { reverse(initialArr, tempCur, i - 1 ); } initialArr[k++] = initialArr[j]; if (j == i - 1 ) { if (k < initialArr.length) { initialArr[k++] = ' ' ; } } } } if (k == 0 ) { return "" ; } else { return new String (initialArr, 0 , (k == initialArr.length) && (initialArr[k - 1 ] != ' ' ) ? k : k - 1 ); } } public void reverse (char [] chars, int begin, int end) { for (int i = begin, j = end; i < j; i++, j--) { chars[i] ^= chars[j]; chars[j] ^= chars[i]; chars[i] ^= chars[j]; } } }
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 class Solution { public String reverseWords (String s) { char [] chars = s.toCharArray(); chars = removeExtraSpaces(chars); reverse(chars, 0 , chars.length - 1 ); reverseEachWord(chars); return new String (chars); } public char [] removeExtraSpaces(char [] chars) { int slow = 0 ; for (int fast = 0 ; fast < chars.length; fast++) { if (chars[fast] != ' ' ) { if (slow != 0 ) chars[slow++] = ' ' ; while (fast < chars.length && chars[fast] != ' ' ) chars[slow++] = chars[fast++]; } } 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--; } } public void reverseEachWord (char [] chars) { int start = 0 ; for (int end = 0 ; end <= chars.length; end++) { if (end == chars.length || chars[end] == ' ' ) { reverse(chars, start, end - 1 ); start = end + 1 ; } } } }
剑指Offer58-II.左旋转字符串※ 建议 :题解中的解法如果没接触过的话,应该会想不到
题目链接: https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/ 文章讲解: https://programmercarl.com/%E5%89%91%E6%8C%87Offer58-II.%E5%B7%A6%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.html
https://www.yuanql.top/2023/06/15/02_leetcode/%E5%89%91%E6%8C%87%20Offer%2058%20-%20II.%20%E5%B7%A6%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2/
题目分析
方案一 1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public String reverseLeftWords (String s, int n) { StringBuilder result = new StringBuilder (s); for (int i = 0 ; i < n; i++) { result.append(result.charAt(0 )); result.deleteCharAt(0 ); } return result.toString(); } }
结果 解答成功: 执行耗时:10 ms,击败了6.18% 的Java用户 内存消耗:42.3 MB,击败了30.40% 的Java用户
分析 时间复杂度: O( n )
空间复杂度: O( m )
方案一的改进 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public String reverseLeftWords (String s, int n) { StringBuilder result = new StringBuilder (); for (int i = n; i < s.length(); i++) { result.append(s.charAt(i)); } for (int i = 0 ; i < n; i++) { result.append(s.charAt(i)); } return result.toString(); } }
结果 解答成功: 执行耗时:4 ms,击败了31.82% 的Java用户 内存消耗:42.5 MB,击败了13.53% 的Java用户
代码随想录 https://programmercarl.com/%E5%89%91%E6%8C%87Offer58-II.%E5%B7%A6%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.html
为了让本题更有意义,提升一下本题难度:不能申请额外空间,只能在本串上操作 。
不能使用额外空间的话,模拟在本串操作要实现左旋转字符串的功能还是有点困难的。
那么我们可以想一下上一题目字符串:花式反转还不够! (opens new window) 中讲过,使用整体反转+局部反转就可以实现反转单词顺序的目的。
这道题目也非常类似,依然可以通过局部反转+整体反转 达到左旋转的目的。
具体步骤为:
反转区间为前n的子串
反转区间为n到末尾的子串
反转整个字符串
最后就可以达到左旋n的目的,而不用定义新的字符串,完全在本串上操作。
例如 :示例1中 输入:字符串abcdefg,n=2
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public String reverseLeftWords (String s, int n) { int len=s.length(); StringBuilder sb=new StringBuilder (s); reverseString(sb,0 ,n-1 ); reverseString(sb,n,len-1 ); return sb.reverse().toString(); } public void reverseString (StringBuilder sb, int start, int end) { while (start < end) { char temp = sb.charAt(start); sb.setCharAt(start, sb.charAt(end)); sb.setCharAt(end, temp); start++; end--; } } }
总结 字符串涉及到字符串位置交换的时候,为了使得空间复杂度为 O(1),可以多次反转以实现其功能。‘
在这篇文章344.反转字符串 (opens new window) ,第一次讲到反转一个字符串应该怎么做,使用了双指针法。
然后发现541. 反转字符串II (opens new window) ,这里开始给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在for循环的表达式上做做文章。
后来在151.翻转字符串里的单词 (opens new window) 中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。
最后再讲到本题,本题则是先局部反转再 整体反转,与151.翻转字符串里的单词 (opens new window) 类似,但是也是一种新的思路。