剑指offer05.替换空格
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1: 输入:s = "We are happy."
输出:"We%20are%20happy."
思路:
1.首先需要考虑扩容后数组的大小。因为空格填充不是一对一填充,首先需要考虑的问题就是数组长度如何定义。
两种方法定义:1.边扩容边开辟新的空间 2.初始化数组的时候定义好大小。
初始化时开辟大小是比较优的选择,避免多次开辟空间造成的系统资源占用。
2.需要考虑如何进行替换操作,原来的数据怎么办。
两种方法:笨方法就是新开一个数组,从头开始遍历,遇到空格就填充对应的填充符,这种方法无脑但是效率低。
最聪明的方法是使用双指针。那么是从前往后填充还是从后往前填充呢?
如果是从前往后进行填充,比如我们移动到第一个空格,我们需要把后面的所有元素后移三个,填充,然后再开始继续遍历,这效率可能还不如用笨方法。
如果是从后往前填充,我们把左指针的值复制到新开辟的空格中,进行替换操作,遇到空格填充空白部分,相当于双指针一次遍历完成多躺赋值操作。
代码:
package com.dwj.LeetCode.String; /** * 替换空格。剑指offer05 * 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 * * 示例 1: 输入:s = "We are happy." * 输出:"We%20are%20happy." */ public class replaceBlank { //使用一个新的对象,复制 str,复制的过程对其判断,是空格则替换,否则直接复制,类似于数组复制 public static String replaceSpace(String s) { if (s == null) { return null; } //选用 StringBuilder 单线程使用,比较快,选不选都行 StringBuilder sb = new StringBuilder(); //使用 sb 逐个复制 s ,碰到空格则替换,否则直接复制 for (int i = 0; i < s.length(); i++) { //s.charAt(i) 为 char 类型,为了比较需要将其转为和 " " 相同的字符串类型 //if (" ".equals(String.valueOf(s.charAt(i)))){} if (s.charAt(i) == ' ') { sb.append("%20"); } else { sb.append(s.charAt(i)); } } return sb.toString(); } //方式二:双指针法 public String replaceSpace2(String s) { if(s == null || s.length() == 0){ return s; } //扩充空间,空格数量2倍 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); } }
LeetCode151.翻转字符串里的单词
给定一个字符串,逐个翻转字符串中的每个单词。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
思路:
整体思路,去掉多余空格,整体翻转,每个单词翻转。
1.首先需要考虑,给出的字符串存在多余的前后空格,需要先清除掉。
可以考虑使用快慢指针,因为:
快指针遍历所有元素,找出符合的元素,赋值给慢指针位置。慢指针只在条件符合的时候进行移动操作
如果慢指针的下标不为起始位置,需要给慢指针的位置多留一个空格,每个单词之前那都有一个空格。然后把快指针位置的单词赋给慢指针即可。
四种不同的写法:
package com.dwj.LeetCode.String; /** * LeetCode151 给定一个字符串,逐个翻转字符串中的每个单词。 * * 示例 1: * 输入: "the sky is blue" * 输出: "blue is sky the" * * 示例 2: * 输入: " hello world! " * 输出: "world! hello" * 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 * * 示例 3: * 输入: "a good example" * 输出: "example good a" * 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 * */ public class reverseWrold { class Solution { /** * 不使用Java内置方法实现 * <p> * 1.去除首尾以及中间多余空格 * 2.反转整个字符串 * 3.反转各个单词 */ public String reverseWords(String s) { // System.out.println("ReverseWords.reverseWords2() called with: s = [" + s + "]"); // 1.去除首尾以及中间多余空格 StringBuilder sb = removeSpace(s); // 2.反转整个字符串 reverseString(sb, 0, sb.length() - 1); // 3.反转各个单词 reverseEachWord(sb); return sb.toString(); } private StringBuilder removeSpace(String s) { // System.out.println("ReverseWords.removeSpace() called with: s = [" + 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++; } // System.out.println("ReverseWords.removeSpace returned: sb = [" + sb + "]"); return sb; } /** * 反转字符串指定区间[start, end]的字符 */ public void reverseString(StringBuilder sb, int start, int end) { // System.out.println("ReverseWords.reverseString() called with: sb = [" + sb + "], start = [" + start + "], end = [" + end + "]"); while (start < end) { char temp = sb.charAt(start); sb.setCharAt(start, sb.charAt(end)); sb.setCharAt(end, temp); start++; end--; } // System.out.println("ReverseWords.reverseString returned: sb = [" + sb + "]"); } 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; } } } //解法二:创建新字符数组填充。时间复杂度O(n) class Solution2 { public String reverseWords(String s) { //源字符数组 char[] initialArr = s.toCharArray(); //新字符数组 char[] newArr = new char[initialArr.length+1];//下面循环添加"单词 ",最终末尾的空格不会返回 int newArrPos = 0; //i来进行整体对源字符数组从后往前遍历 int i = initialArr.length-1; while(i>=0){ while(i>=0 && initialArr[i] == ' '){i--;} //跳过空格 //此时i位置是边界或!=空格,先记录当前索引,之后的while用来确定单词的首字母的位置 int right = i; while(i>=0 && initialArr[i] != ' '){i--;} //指定区间单词取出(由于i为首字母的前一位,所以这里+1,),取出的每组末尾都带有一个空格 for (int j = i+1; j <= right; j++) { newArr[newArrPos++] = initialArr[j]; if(j == right){ newArr[newArrPos++] = ' ';//空格 } } } //若是原始字符串没有单词,直接返回空字符串;若是有单词,返回0-末尾空格索引前范围的字符数组(转成String返回) if(newArrPos == 0){ return ""; }else{ return new String(newArr,0,newArrPos-1); } } } //解法三:双反转+移位,在原始数组上进行反转。空间复杂度O(1) class Solution3 { /** * 思路: * ①反转字符串 "the sky is blue " => " eulb si yks eht" * ②遍历 " eulb si yks eht",每次先对某个单词进行反转再移位 * 这里以第一个单词进行为演示:" eulb si yks eht" ==反转=> " blue si yks eht" ==移位=> "blue si yks eht" */ public String reverseWords(String s) { //步骤1:字符串整体反转(此时其中的单词也都反转了) 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) { //遍历结束 //避免越界情况,例如=> "asdasd df f",不加判断最后就会数组越界 if (k < initialArr.length) { initialArr[k++] = ' '; } } } } if (k == 0) { return ""; } else { //参数三:以防出现如"asdasd df f"=>"f df asdasd"正好凑满不需要省略空格情况 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]; } } } //解法三:双反转+移位,在原始数组上进行反转。空间复杂度O(1) class Solution { /** * 思路: * ①反转字符串 "the sky is blue " => " eulb si yks eht" * ②遍历 " eulb si yks eht",每次先对某个单词进行反转再移位 * 这里以第一个单词进行为演示:" eulb si yks eht" ==反转=> " blue si yks eht" ==移位=> "blue si yks eht" */ public String reverseWords(String s) { //步骤1:字符串整体反转(此时其中的单词也都反转了) 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) { //遍历结束 //避免越界情况,例如=> "asdasd df f",不加判断最后就会数组越界 if (k < initialArr.length) { initialArr[k++] = ' '; } } } } if (k == 0) { return ""; } else { //参数三:以防出现如"asdasd df f"=>"f df asdasd"正好凑满不需要省略空格情况 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]; } } } /* * 解法四:时间复杂度 O(n) * 参考卡哥 c++ 代码的三步骤:先移除多余空格,再将整个字符串反转,最后把单词逐个反转 * 有别于解法一 :没有用 StringBuilder 实现,而是对 String 的 char[] 数组操作来实现以上三个步骤 */ class Solution4 { //用 char[] 来实现 String 的 removeExtraSpaces,reverse 操作 public String reverseWords(String s) { char[] chars = s.toCharArray(); //1.去除首尾以及中间多余空格 chars = removeExtraSpaces(chars); //2.整个字符串反转 reverse(chars, 0, chars.length - 1); //3.单词反转 reverseEachWord(chars); return new String(chars); } //1.用 快慢指针 去除首尾以及中间多余空格,可参考数组元素移除的题解 public char[] removeExtraSpaces(char[] chars) { int slow = 0; for (int fast = 0; fast < chars.length; fast++) { //先用 fast 移除所有空格 if (chars[fast] != ' ') { //在用 slow 加空格。 除第一个单词外,单词末尾要加空格 if (slow != 0) chars[slow++] = ' '; //fast 遇到空格或遍历到字符串末尾,就证明遍历完一个单词了 while (fast < chars.length && chars[fast] != ' ') chars[slow++] = chars[fast++]; } } //相当于 c++ 里的 resize() 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--; } } //3.单词反转 public void reverseEachWord(char[] chars) { int start = 0; //end <= s.length() 这里的 = ,是为了让 end 永远指向单词末尾后一个位置,这样 reverse 的实参更好设置 for (int end = 0; end <= chars.length; end++) { // end 每次到单词末尾后的空格或串尾,开始反转单词 if (end == chars.length || chars[end] == ' ') { reverse(chars, start, end - 1); start = end + 1; } } } } }
标签:String,Day8,int,代码,随想录,initialArr,空格,length,chars From: https://www.cnblogs.com/dwj-ngu/p/16818882.html