代码随想录算法训练营第八天 | 344.反转字符串 ● 541. 反转字符串II ● 剑指Offer 05.替换空格 ● 151.翻转字符串里的单词 ● 剑指Offer58-II.左旋转字符串
今日任务
● 344.反转字符串
● 541. 反转字符串II
● 剑指Offer 05.替换空格
● 151.翻转字符串里的单词
● 剑指Offer58-II.左旋转字符串
详细布置
344.反转字符串
建议: 本题是字符串基础题目,就是考察 reverse 函数的实现,同时也明确一下 平时刷题什么时候用 库函数,什么时候 不用库函数
题目链接/文章讲解/视频讲解:https://programmercarl.com/0344.%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.html
思路:临时变量temp,遍历到长度一半,交换s[i] 与 s[s.length - i -1]。
class Solution { public void reverseString(char[] s) { char temp = ' '; for (int i = 0; i < s.length / 2; i++) { temp = s[i]; s[i] = s[s.length - 1 - i]; s[s.length - 1 - i] = temp; } } }
2)双指针
class Solution { public void reverseString(char[] s) { int l = 0; int r = s.length - 1; while (l < r) { s[l] ^= s[r]; //构造 a ^ b 的结果,并放在 a 中 s[r] ^= s[l]; //将 a ^ b 这一结果再 ^ b ,存入b中,此时 b = a, a = a ^ b s[l] ^= s[r]; //a ^ b 的结果再 ^ a ,存入 a 中,此时 b = a, a = b 完成交换 l++; r--; } } }
学习了用异或运算(^)来交换元素
541. 反转字符串II
建议:本题又进阶了,自己先去独立做一做,然后在看题解,对代码技巧会有很深的体会。
题目链接/文章讲解/视频讲解:https://programmercarl.com/0541.%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2II.html
//题目的意思其实概括为 每隔2k个反转前k个,尾数不够k个时候全部反转 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; //这里是判断尾数够不够k个来取决end指针的位置 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); } }
解法2
class Solution { public String reverseStr(String s, int k) { char[] ch = s.toCharArray(); // 1. 每隔 2k 个字符的前 k 个字符进行反转 for (int i = 0; i< ch.length; i += 2 * k) { // 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符 if (i + k <= ch.length) { reverse(ch, i, i + k -1); continue; } // 3. 剩余字符少于 k 个,则将剩余字符全部反转 reverse(ch, i, ch.length - 1); } return new String(ch); } // 定义翻转函数 public void reverse(char[] ch, int i, int j) { for (; i < j; i++, j--) { char temp = ch[i]; ch[i] = ch[j]; ch[j] = temp; } } }
剑指Offer 05.替换空格
建议:对于线性数据结构,填充或者删除,后序处理会高效的多。好好体会一下。
题目链接/文章讲解:https://programmercarl.com/%E5%89%91%E6%8C%87Offer05.%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC.html
方法一:
//使用一个新的对象,复制 str,复制的过程对其判断,是空格则替换,否则直接复制,类似于数组复制
class Solution { public String replaceSpace(String s) { 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(); } }
总结:
学习StringBuilder的用法:
String类: 字符串的底层是一个被final修饰的数组,不能改变,是一个常量
StringBuilder类:字符串缓冲区,底层是一个没有被final修饰的数组,可改变长度,可提高字符串的操作效率
构造方法: public stringBuilder():构造一个空的stringBuilder容器 public stringBuilder(String str):构造一个StringBuilder容器,并将str添加进去 成员方法: public StringBuilder append(Object obj):添加任意类的字符串形式,并返回当前对象 eg. StringBuilder sb1 = new StringBuilder(); sb1.append(1); StringBuilder sb2 = sb1.append("abc");//sb2 == sb1 //由于返回值为当前对象,所以可以进行链式编程 sb1.append(1).append("abc").append(true); public String toString():将当前StringBuilder对象转换为String对象 public StringBuilder reverse():反转内容,将容器中的每一个字母反转,eg."abcd"-->"dcba"
方法二:
双指针
class Solution { public String replaceSpace(String s) { StringBuilder sb = new StringBuilder(); //扩充空间,有一个空格,长度 + 2 for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == ' '){ sb.append(" "); } } //若是没有空格直接返回 if (sb.length() == 0){ return s; } //有空格情况 定义两个指针 int l = s.length() - 1;//左指针:指向原始字符串最后一个位置 s += sb.toString(); int r = s.length() - 1;//右指针:指向扩展字符串的最后一个位置 char[] chars = s.toCharArray(); while (l >= 0){ if (chars[l] == ' '){ chars[r--] = '0'; chars[r--] = '2'; chars[r] = '%'; }else { chars[r] = chars[l]; } l--; r--; } return new String(chars); } }
151.翻转字符串里的单词
建议:这道题目基本把 刚刚做过的字符串操作 都覆盖了,不过就算知道解题思路,本题代码并不容易写,要多练一练。
题目链接/文章讲解/视频讲解: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
算法思路:
从后向前,不停的把单词添加到 StringBuilder 中,最终返回结果
关键点:
如何修剪掉两端空格;
如何把单词反转过来;
如何跳过中间连续的空格。
class Solution { public String reverseWords(String s) { char[] charArray=s.toCharArray(); int left=0,right=s.length()-1; // 清除字符串两边的空格 // 清除左边 while(charArray[left]==' '){ left++; } // 清除右边 while(charArray[right]==' '){ right--; } StringBuilder sb=new StringBuilder(); // 开始添加单词 while(left<=right){ int index=right; // index 向左遍历找到第一个空格 while(index>=left&&charArray[index]!=' '){ index--; } // 现在 index 已经找到第一个空格,i=index+1 后移到字符串出现的位置 // 添加字符串 for(int i=index+1;i<=right;i++){ sb.append(charArray[i]); } // 如果不是最后一个单词,就添加空格 if(index>left) sb.append(' '); // 使用 index 指针 跳过中间可能出现的空格 while(index>=left&&charArray[index]==' '){ index--; } // 把 right 放到下一个单词出现的位置,继续循环 right=index; } return sb.toString(); } } 解答成功: 执行耗时:2 ms,击败了96.89% 的Java用户 内存消耗:41.5 MB,击败了39.25% 的Java用户
剑指Offer58-II.左旋转字符串
class Solution { public String reverseLeftWords(String s, int n) { String s1 = reverse(s); String s2 = s1.substring(0, s.length() - n); String s3 = s1.substring(s.length() - n, s.length()); return reverse(s2) + reverse(s3); } public String reverse(String str){ char[] chars = str.toCharArray(); int l = 0, r = chars.length - 1; while (l < r){ chars[l] ^= chars[r]; chars[r] ^= chars[l]; chars[l] ^= chars[r]; l++; r--; } return new String(chars); } } 解答成功: 执行耗时:3 ms,击败了43.92% 的Java用户 内存消耗:41.7 MB,击败了15.16% 的Java用户
解2:
class Solution { public String reverseLeftWords(String s, int n) { int len = s.length(); if (n <= 0 || n >= len){ return s; } StringBuilder sb = new StringBuilder(s); reverseString(sb, 0, len - 1); reverseString(sb, 0, len - n - 1); reverseString(sb, len - n, len - 1); return sb.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--; } } } 解答成功: 执行耗时:3 ms,击败了43.92% 的Java用户 内存消耗:41.7 MB,击败了15.16% 的Java用户
总结
在这篇文章344.反转字符串(opens new window),第一次讲到反转一个字符串应该怎么做,使用了双指针法。
然后发现541. 反转字符串II(opens new window),这里开始给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。
后来在151.翻转字符串里的单词(opens new window)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。
最后再讲到本题,本题则是先局部反转再 整体反转,与151.翻转字符串里的单词(opens new window)类似。