344.反转字符串 文章:代码随想录 (programmercarl.com)
题目:字符串基础操作! | LeetCode:344.反转字符串_哔哩哔哩_bilibili
视频:344. 反转字符串
【思路】
方法:双指针法
如果题目的实现逻辑为库函数的话,尽量手写。
swap函数的实现逻辑:
swap可以有两种实现。
一种就是常见的交换数值:
1 2 3 int tmp = s[i];s[i] = s[j]; s[j] = tmp;
一种就是通过位运算:
1 2 3 s[i] ^= s[j]; s[j] ^= s[i]; s[i] ^= s[j];
1 2 3 4 5 6 7 8 9 10 11 class Solution { public void reverseString (char [] s) { int j = s.length-1 ; for (int i = 0 ; i < s.length/2 ;i++){ char t = s[i]; s[i] = s[j]; s[j] = t; j--; } } }
1 2 3 4 5 6 7 func reverseString (s []byte ) { j := len (s)-1 for i:=0 ;i<len (s)/2 ;i++{ s[i] , s[j] = s[j],s[i] j-- } }
541.反转字符串II 文章:2. 反转字符串II
题目:541. 反转字符串 II - 力扣(LeetCode)
视频:字符串操作进阶! | LeetCode:541. 反转字符串II_哔哩哔哩_bilibili
需要注意for循环的临界条件!
需要注意题目要求的是反转2k段中的前k段,这样可能会面临k >s.length
的问题,因此我们选取右指针时需要取s.length()-1, l+k-1
的最小值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public String reverseStr (String s, int k) { char []c = s.toCharArray(); for (int i = 0 ; i < c.length;i +=2 *k){ int l = i; int r = Math.min(c.length-1 ,l+k-1 ); while (l < r){ c[l] ^= c[r]; c[r] ^= c[l]; c[l] ^= c[r]; l++; r--; } } return new String (c); } }
划分区间,左指针到 k 段的值翻转后加入res,k 到 2k 段的值直接加入res,然后移动指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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(); } }
使用 if 语句来判断 右指针的边界值是否会大于字符串的长度:如果小于等于则切片范围正常取i+k
,大于则切片范围取字符串的最大长度length
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func reverseStr (s string , k int ) string { ss:=[]byte (s) length := len (s) for i := 0 ; i < len (s);i+=2 *k{ if i+k <= length{ reverse(ss[i:i+k]) }else { reverse(ss[i:length]) } } return string (ss) } func reverse (b []byte ) { left := 0 right := len (b)-1 for left < right { b[left],b[right] = b[right],b[left] left++ right-- } }
剑指Offer 05.替换空格 文章:3. 替换空格
题目:已经被替换了
请实现一个函数,把字符串 s 中的每个空格替换成”%20”。
示例 1: 输入:s = “We are happy.” 输出:”We%20are%20happy.”
【思路】
如果想把这道题目做到极致,就不要只用额外的辅助空间了!
首先扩充数组到每个空格替换成”%20”之后的大小。
然后从后向前替换空格,也就是双指针法,过程如下:
i指向新长度的末尾,j指向旧长度的末尾。
有同学问了,为什么要从后向前填充,从前向后填充不行么?
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。
其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这么做有两个好处:
不用申请新数组。
从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
时间复杂度,空间复杂度均超过100%的用户。
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 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); }
# Go:
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 func replaceSpace (s string ) string { b := []byte (s) result := make ([]byte , 0 ) for i := 0 ; i < len (b); i++ { if b[i] == ' ' { result = append (result, []byte ("%20" )...) } else { result = append (result, b[i]) } } return string (result) } func replaceSpace (s string ) string { b := []byte (s) length := len (b) spaceCount := 0 for _, v := range b { if v == ' ' { spaceCount++ } } resizeCount := spaceCount * 2 tmp := make ([]byte , resizeCount) b = append (b, tmp...) i := length - 1 j := len (b) - 1 for i >= 0 { if b[i] != ' ' { b[j] = b[i] i-- j-- } else { b[j] = '0' b[j-1 ] = '2' b[j-2 ] = '%' i-- j = j - 3 } } return string (b) }
151.反转字符串里的单词 文章:4. 翻转字符串里的单词
题目:151. 反转字符串中的单词
视频:字符串复杂操作拿捏了! | LeetCode:151.翻转字符串里的单词_哔哩哔哩_bilibili
【思路】
先反转整个字符串,这时每个单词的位置以及字母顺序都进行了反转,和目标字符串的区别在于每个单词的字母顺序不一样,然后再将每一个单词的字母顺序进行反转即可。
不要使用辅助空间,空间复杂度要求为O(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 52 53 54 55 56 57 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) { StringBuilder sb = new StringBuilder (); int start = 0 ; int end = s.length()-1 ; while (s.charAt(start) == ' ' )start++; while (s.charAt(end)==' ' )end--; while (start <= end){ char c = s.charAt(start); if (c !=' ' || sb.charAt(sb.length()-1 ) != ' ' ){ sb.append(c); } start++; } return sb; } private 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 len = sb.length(); while (start < len){ while (end < len && 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 32 33 34 35 36 37 38 39 40 41 func reverseWords (s string ) string { b :=[]byte (s) slow := 0 for i :=0 ;i < len (s);i++{ if b[i]!=' ' { if slow != 0 { b[slow] = ' ' slow++ } for i<len (b) && b[i]!=' ' { b[slow]=b[i] slow++ i++ } } } b =b[0 :slow] reverse(b) last:=0 for i :=0 ;i<=len (b);i++{ if i == len (b)|| b[i] ==' ' { reverse(b[last:i]) last=i+1 } } return string (b) } func reverse (b []byte ) { left :=0 right := len (b)-1 for left<right{ b[left],b[right] = b[right],b[left] left++ right-- } }
Offer58-II.左旋转字符串 文章:5. 左旋转字符串
题目:LCR 182. 动态口令
【思路】
不申请额外空间、只能在本串上操作。
这样的话我们可以使用整体反转+局部反转 实现左旋转的目的。
1、反转前n位子串
2、反转n到末尾的子串
3、反转整个字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public String dynamicPassword (String password, int target) { StringBuilder sb = new StringBuilder (password); reverse(sb,0 ,target-1 ); reverse(sb,target,password.length()-1 ); reverse(sb,0 ,password.length()-1 ); return sb.toString(); } private void reverse (StringBuilder sb,int start,int end) { while (start < end){ char c = sb.charAt(start); sb.setCharAt(start,sb.charAt(end)); sb.setCharAt(end,c); start++; end--; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func dynamicPassword (password string , target int ) string { b := []byte (password) reverseString(b,0 ,target-1 ) reverseString(b,target,len (b)-1 ) reverseString(b,0 ,len (b)-1 ) return string (b) } func reverseString (b []byte ,start int , end int ) { for start < end{ b[start],b[end] = b[end],b[start] start++ end-- } }
【算法总结】
反转字符串 的题目使用双指针法 对字符串进行操作即可、以及使用的数据类型为StringBuilder
。
反转字符串里的单词 需要我们先去除字符串里面多余的空格 ,然后反转整个字符串 ,最后再反转每个单词 (依靠空格划分)->反转[start,end]范围内的字符串
左旋转字符 和反转字符串里的单词类似,都是对某一段的字符串进行多次反转操作 ,即可得出题解。
字符串 的题目要留意**for
循环的边界条件**,很容易犯错。
【语法总结】
字符串交换:
一种就是常见的交换数值:
1 2 3 int tmp = s[i];s[i] = s[j]; s[j] = tmp;
一种就是通过位运算:
1 2 3 s[i] ^= s[j]; s[j] ^= s[i]; s[i] ^= s[j];
我们可以将String
类型的字符串转化为StringBuffer
或StringBuilder
的形式对字符串进行操作。
1 2 StringBuilder sb = new Stringbuilder (s)sb.toString();
将string
类型的字符串转化为[]byte
数组进行操作。