代码随想录算法训练营第08天 | LeetCode 344.反转字符串,541. 反转字符串2,剑指Offer 05.替换空格,151.翻转字符串里的单词,剑指Offer58-II.左旋转字符串

LeetCode [344. 反转字符串]

题目:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。

  • 示例 1:

    输入:s = ["h","e","l","l","o"]
    输出:["o","l","l","e","h"]
    
  • 示例 2:

    输入:s = ["H","a","n","n","a","h"]
    输出:["h","a","n","n","a","H"]
    

思路

  1. 双指针法

    因为字符串也是一种数组,所以元素在内存中是连续分布,这就决定了反转链表和反转字符串方式上还是有所差异的。对于字符串,我们定义两个指针(也可以说是索引下标),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。

//双指针法
class Solution {
    public void reverseString(char[] s) {
        int right = s.length - 1;
        int left = 0;
        while(left < right){
            char temp = s[right];
            s[right] = s[left];
            s[left] = temp;
            // s[left] ^= s[right]; //构造 a ^ b 的结果,并放在 a 中
            // s[right] ^= s[left]; //将 a ^ b 这一结果再 ^ b ,存入b中,此时 b = a, a = a ^ b
            // s[left] ^= s[right]; //a ^ b 的结果再 ^ a ,存入 a 中,此时 b = a, a = b 完成交换
            right--;
            left++;
        }
    }
}

扩展:位运算

1. 位运算概览

符号描述运算规则
&两个位都为1时,结果才为1
|两个位都为0时,结果才为0
^异或两个位相同为0,相异为1
~取反0变1,1变0
<<左移各二进位全部左移若干位,高位丢弃,低位补0
>>右移各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)

2. 按位与运算符(&)

定义:参加运算的两个数据,按二进制位进行"与"运算。

运算规则:

0&0=0  0&1=0  1&0=0  1&1=1

总结:两位同时为1,结果才为1,否则结果为0。

例如:3&5 即 0000 0011& 0000 0101 = 0000 0001,因此 3&5 的值得1。

注意:负数按补码形式参加按位与运算。

与运算的用途:

1)清零

如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。

2)取一个数的指定位

比如取数 X=1010 1110 的低4位,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位与运算(X&Y=0000 1110)即可得到X的指定位。

3)判断奇偶

只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。

3. 按位或运算符(|)

定义:参加运算的两个对象,按二进制位进行"或"运算。

运算规则:

0|0=0  0|1=1  1|0=1  1|1=1

总结:参加运算的两个对象只要有一个为1,其值为1。

例如:3|5即 0000 0011| 0000 0101 = 0000 0111,因此,3|5的值得7。

注意:负数按补码形式参加按位或运算。

或运算的用途:

1)常用来对一个数据的某些位设置为1

比如将数 X=1010 1110 的低4位设置为1,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位或运算(X|Y=1010 1111)即可得到。

4. 异或运算符(^)

定义:参加运算的两个数据,按二进制位进行"异或"运算。

运算规则:

0^0=0  0^1=1  1^0=1  1^1=0

总结:参加运算的两个对象,如果两个相应位相同为0,相异为1。

异或的几条性质:

  • 1、交换律
  • 2、结合律 (ab)c == a(bc)
  • 3、对于任何数x,都有 xx=0,x0=x
  • 4、自反性: abb=a^0=a;

异或运算的用途:

1)翻转指定位

比如将数 X=1010 1110 的低4位进行翻转,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行异或运算(X^Y=1010 0001)即可得到。

2)与0相异或值不变

例如:1010 1110 ^ 0000 0000 = 1010 1110

3)交换两个数

实例

s[i] ^= s[j];
s[j] ^= s[i];
s[i] ^= s[j];

5. 取反运算符 (~)

定义:参加运算的一个数据,按二进制进行"取反"运算。

运算规则:

~1=0
~0=1

总结:对一个二进制数按位取反,即将0变1,1变0。

取反运算的用途:

1)使一个数的最低位为零

使a的最低位为0,可以表示为:a & 1。1的值为 1111 1111 1111 1110,再按"与"运算,最低位一定为0。因为" ~"运算符的优先级比算术运算符、关系运算符、逻辑运算符和其他运算符都高。

6. 左移运算符(<<)

定义:将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。

设 a=1010 1110,a = a<< 2 将a的二进制位左移2位、右补0,即得a=1011 1000。

若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。

7. 右移运算符(>>)

定义:将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。

例如:a=a>>2 将a的二进制位右移2位,左补0 或者 左补1得看被移数是正还是负。

操作数每右移一位,相当于该数除以2。

8. 复合赋值运算符

位运算符与赋值运算符结合,组成新的复合赋值运算符,它们是:

&=        例:a&=b    相当于     a=a&b

|=        例:a|=b    相当于     a=a|b

>>=      例:a>>=b   相当于     a=a>>b

<<=      例:a<<=b     相当于      a=a<<b

^=        例:a^=b    相当于   a=a^b

运算规则:和前面讲的复合赋值运算符的运算规则相似。

不同长度的数据进行位运算:如果两个不同长度的数据进行位运算时,系统会将二者按右端对齐,然后进行位运算。

以"与运算"为例说明如下:我们知道在C语言中long型占4个字节,int型占2个字节,如果一个long型数据与一个int型数据进行"与运算",右端对齐后,左边不足的位依下面三种情况补足,

  • 1)如果整型数据为正数,左边补16个0。

  • 2)如果整型数据为负数,左边补16个1。

  • 3)如果整形数据为无符号数,左边也补16个0。

  • 如:long a=123;int b=1;计算a& b。

  • 如:long a=123;int b=-1;计算a& b。

  • 如:long a=123;unsigned intb=1;计算a & b。


LeetCode [541. 反转字符串 II]

题目:给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。

  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

  • 示例 1:

    输入:s = "abcdefg", k = 2
    输出:"bacdfeg"
    
  • 示例 2:

    输入:s = "abcd", k = 2
    输出:"bacd"
    

思路

//模拟法
//题目的意思其实概括为 每隔2k个反转前k个,尾数不够k个时候全部反转
class Solution {
    public String reverseStr(String s, int k) {
        //先将String字符串转为字符数组
        char[] str = s.toCharArray();
        //获取字符数组的长度
        int len = str.length;
        //每隔 2k 个字符进行遍历,因为要找的也就是每2 * k 区间的起点,这样写,程序会高效很多。
        for(int i = 0;i < len;i += 2 * k){
            //定义左指针
            int left = i;
            //定义右指针,这里是判断尾数够不够k个来取决end指针的位置
            int right = Math.min(len - 1,left + k - 1);
            while(left < right){
                str[left] ^= str[right];
                str[right] ^= str[left];
                str[left] ^= str[right];
                left++;
                right--;
            }
        }
        //将字符数组转为字符串String对象
        return new String(str);
    }
}
//模拟法2
class Solution {
    public String reverseStr(String s, int k) {
        //先将String字符串转为字符数组
        char[] ch = s.toCharArray();
        //每隔 2k 个字符的前 k 个字符进行反转
        for(int i = 0;i < ch.length;i += 2 * k){
            //剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
            if(i + k <= ch.length){
                reverse(ch,i,i + k - 1);
            //剩余字符少于 k 个,则将剩余字符全部反转
            }else{
                reverse(ch,i,ch.length - 1);
            }
        }
        return new String(ch);
    }
    //定义反转函数
    public void reverse(char[] ch,int left,int right){
        while(left < right){
            ch[left] ^= ch[right];
            ch[right] ^= ch[left];
            ch[left] ^= ch[right];
            left++;
            right--;
        }
    }
}

扩展:return new String();StringBuffer;StringBuilder

1. new String()

A a1 = new A();
它代表A是类,a1是引用,a1不是对象,new A()才是对象,a1引用指向new A()这个对象。

在JAVA里,“=”不能被看成是一个赋值语句,它不是在把一个对象赋给另外一个对象,它的执行过程实质上是将右边对象的地址传给了左边的引用,使得左边的引用指向了右边的对象。JAVA表面上看起来没有指针,但它的引用其实质就是一个指针,引用里面存放的并不是对象,而是该对象的地址,使得该引用指向了对象。

A a2;
它代表A是类,a2是引用,a2不是对象,a2所指向的对象为空null;

a2 = a1;
它代表,a2是引用,a1也是引用,a1所指向的对象的地址传给了a2(传址),使得a2和a1指向了同一对象。

Person person=new Person() 这里person 可以理解为引用(借助内存地址来理解), 而person就是操作这个内存地址来操作这个对象的。

综上所述,可以简单的记为,在初始化时,“=”语句左边的是引用,右边new出来的是对象。
在后面的左右都是引用的“=”语句时,左右的引用同时指向了右边引用所指向的对象。

2. StringBuffer

img

在 Java 中,除了通过 String 类创建和处理字符串之外,还可以使用 StringBuffer 类来处理字符串。StringBuffer 类可以比 String 类更高效地处理字符串。

因为 StringBuffer 类是可变字符串类,创建 StringBuffer 类的对象后可以随意修改字符串的内容。每个 StringBuffer 类的对象都能够存储指定容量的字符串,如果字符串的长度超过了 StringBuffer 类对象的容量,则该对象的容量会自动扩大。

1.概述

StringBuffer:字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用 StringBuffer,如果想转成 String 类型,可以调用 StringBuffer 的 toString() 方法。

Java.lang.StringBuffer 线程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。

StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。

  • append 方法始终将这些字符添加到缓冲区的末端;
  • insert 方法则在指定的点添加字符。

例如,如果 z 引用一个当前内容是 start 的字符串缓冲区对象,则此方法调用 z.append(“le”) 会使字符串缓冲区包含 startle ,而 z.insert(4, “le”) 将更改字符串缓冲区,使之包含 starlet

2.构造方法

StringBuffer 类提供了 3 个构造方法来创建一个字符串,如下所示:

  • StringBuffer() 构造一个空的字符串缓冲区,并且初始化为 16 个字符的容量。

  • StringBuffer(int length) 创建一个空的字符串缓冲区,并且初始化为指定长度 length 的容量。

  • StringBuffer(String str) 创建一个字符串缓冲区,并将其内容初始化为指定的字符串内容 str,字符串缓冲区的初始容量为 16 加上字符串 str 的长度。

    //下述代码声明了 3 个 StringBuffer 对象 str1、str2 和 str3,并分别对其进行初始化。
    //str1.capacity() 用于查看 str1 的容量,接着以同样的方式对 str2 和 str3 进行容量查看的操作。
    // 定义一个空的字符串缓冲区,含有16个字符的容量
    StringBuffer str1 = new StringBuffer();
    
    // 定义一个含有10个字符容量的字符串缓冲区
    StringBuffer str2 = new StringBuffer(10);
    
    // 定义一个含有(16+4)的字符串缓冲区,"青春无悔"为4个字符
    StringBuffer str3 = new StringBuffer("青春无悔");
    /*
    *输出字符串的容量大小
    *capacity()方法返回字符串的容量大小
    */
    System.out.println(str1.capacity());    // 输出 16
    System.out.println(str2.capacity());    // 输出 10
    System.out.println(str3.capacity());    // 输出 20
    
3.追加字符串

StringBuffer 类的 append() 方法用于向原有 StringBuffer 对象中追加字符串。该方法的语法格式如下:

StringBuffer 对象.append(String str)

该方法的作用是追加内容到当前 StringBuffer 对象的末尾,类似于字符串的连接。调用该方法以后,StringBuffer 对象的内容也发生了改变,例如:

StringBuffer buffer = new StringBuffer("hello,");    // 创建一个 StringBuffer 对象
String str = "World!";
buffer.append(str);    // 向 StringBuffer 对象追加 str 字符串
System.out.println(buffer.substring(0));    // 输出:Hello,World!
4.替换字符

StringBuffer 类的 setCharAt() 方法用于在字符串的指定索引位置替换一个字符。该方法的语法格式如下:

StringBuffer 对象.setCharAt(int index, char ch);

该方法的作用是修改对象中索引值为 index 位置的字符为新的字符 ch,例如:

StringBuffer sb = new StringBuffer("hello");
sb.setCharAt(1,'E');
System.out.println(sb);    // 输出:hEllo
sb.setCharAt(0,'H');
System.out.println(sb);    // 输出:HEllo
sb.setCharAt(2,'p');
System.out.println(sb);    // 输出:HEplo
5.反转字符串

StringBuffer 类中的 reverse() 方法用于将字符串序列用其反转的形式取代。该方法的语法格式如下:

StringBuffer 对象.reverse();

使用 StringBuffer 类中的 reverse() 方法对字符串进行反转的示例如下:

StringBuffer sb = new StringBuffer("java");
sb.reverse();
System.out.println(sb);    // 输出:avaj
6.删除字符串

StringBuffer 类提供了 deleteCharAt() 和 delete() 两个删除字符串的方法,下面详细介绍。

1. deleteCharAt() 方法

deleteCharAt() 方法用于移除序列中指定位置的字符,该方法的语法格式如下:

StringBuffer 对象.deleteCharAt(int index);

deleteCharAt() 方法的作用是删除指定位置的字符,然后将剩余的内容形成一个新的字符串。例如:

StringBuffer sb = new StringBuffer("She");
sb.deleteCharAt(2);
System.out.println(sb);    // 输出:Sh

执行该段代码,将字符串 sb 中索引值为 2 的字符删除,剩余的内容组成一个新的字符串,因此对象 sb 的值为 Sh。

2. delete() 方法

delete() 方法用于移除序列中子字符串的字符,该方法的语法格式如下:

StringBuffer 对象.delete(int start,int end);

其中,start 表示要删除字符的起始索引值(包括索引值所对应的字符),end 表示要删除字符串的结束索引值(不包括索引值所对应的字符)。该方法的作用是删除指定区域以内的所有字符,例如:

StringBuffer sb = new StringBuffer("hello jack");
sb.delete(2,5);
System.out.println(sb);    // 输出:he jack
sb.delete(2,5);
System.out.println(sb);    // 输出:heck

执行该段代码,将字符串“hello jack”索引值为 2(包括)到索引值为 5(不包括)之间的所有字符删除,因此输出的新的字符串的值为“he jack”。

3. StringBuilder

1.概述

当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。

和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。

在使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,所以如果需要对字符串进行修改推荐使用 StringBuffer。

StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。

由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。

字符串变量(非线程安全)。在内部,StringBuilder 对象被当作是一个包含字符序列的变长数组。

java.lang.StringBuilder 是一个可变的字符序列,是 JDK5.0 新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。

2. 构造方法
构造方法描述
StringBuilder()创建一个容量为16的StringBuilder对象(16个空元素)
StringBuilder(CharSequence cs)创建一个包含cs的StringBuilder对象,末尾附加16个空元素
StringBuilder(int initCapacity)创建一个容量为initCapacity的StringBuilder对象
StringBuilder(String s)创建一个包含s的StringBuilder对象,末尾附加16个空元素

在大部分情况下,StringBuilder > StringBuffer。这主要是由于前者不需要考虑线程安全。

// Java Code to illustrate StringBuilder

import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

public class GFG1 {
	public static void main(String[] argv) throws Exception
	{
		// Create a StringBuilder object
		// using StringBuilder() constructor
		StringBuilder str = new StringBuilder();

		str.append("GFG");

		// print string
		System.out.println("String = " + str.toString());

		// create a StringBuilder object
		// using StringBuilder(CharSequence) constructor
		StringBuilder str1
			= new StringBuilder("AAAABBBCCCC");

		// print string
		System.out.println("String1 = " + str1.toString());

		// create a StringBuilder object
		// using StringBuilder(capacity) constructor
		StringBuilder str2 = new StringBuilder(10);

		// print string
		System.out.println("String2 capacity = "
						+ str2.capacity());

		// create a StringBuilder object
		// using StringBuilder(String) constructor
		StringBuilder str3
			= new StringBuilder(str1.toString());

		// print string
		System.out.println("String3 = " + str3.toString());
	}
}

输出:

String = GFG
String1 = AAAABBBCCCC
String2 capacity = 10
String3 = AAAABBBCCCC
3. 方法
  1. StringBuilder append(X x):此方法将 X 类型参数的字符串表示附加到序列中。
  2. StringBuilder appendCodePoint(int codePoint):此方法将 codePoint 参数的字符串表示附加到此序列。
  3. int capacity():此方法返回当前容量。
  4. char charAt(int index):此方法返回此序列中指定索引处的 char 值。
  5. IntStream chars():此方法返回一个 int 流,对来自该序列的 char 值进行零扩展。
  6. int codePointAt(int index):此方法返回指定索引处的字符(Unicode 代码点)。
  7. int codePointBefore(int index):此方法返回指定索引之前的字符(Unicode 代码点)。
  8. int codePointCount(int beginIndex, int endIndex):此方法返回此序列的指定文本范围内的 Unicode 代码点数。
  9. IntStream codePoints():此方法从该序列返回代码点值流。
  10. StringBuilder delete(int start, int end):此方法删除此序列的子字符串中的字符。
  11. StringBuilder deleteCharAt(int index):此方法删除此序列中指定位置的字符。
  12. void ensureCapacity(int minimumCapacity):此方法确保容量至少等于指定的最小值。
  13. void getChars(int srcBegin, int srcEnd, char[ ] dst, int dstBegin):此方法字符从该序列复制到目标字符数组 dst。
  14. int indexOf():此方法返回此字符串中第一次出现指定子字符串的索引。
  15. StringBuilder insert(int offset, boolean b):此方法将 booalternatelean 参数的字符串表示形式插入到此序列中。
  16. StringBuilder insert():此方法将 char 参数的字符串表示形式插入到此序列中。
  17. int lastIndexOf():此方法返回此字符串中最后一次出现指定子字符串的索引。
  18. int length():此方法返回长度(字符数)。
  19. int offsetByCodePoints(int index, int codePointOffset):此方法返回此序列中的索引,该索引与给定索引偏移 codePointOffset 代码点。
  20. StringBuilder replace(int start, int end, String str):此方法将此序列的子字符串中的字符替换为指定字符串中的字符。
  21. StringBuilder reverse():此方法导致此字符序列被序列的反向替换。
  22. void setCharAt(int index, char ch) : 在这个方法中,指定索引处的字符被设置为 ch。
  23. void setLength(int newLength):此方法设置字符序列的长度。
  24. CharSequence subSequence(int start, int end):这个方法返回一个新的字符序列,它是这个序列的子序列。
  25. String substring():此方法返回一个新字符串,其中包含当前包含在此字符序列中的字符子序列。
  26. String toString():此方法返回一个表示此序列中数据的字符串。
  27. void trimToSize():此方法尝试减少用于字符序列的存储空间。
4. String、StringBuffer、StringBuilder的区别

String 类型和 StringBuffer 的主要性能区别:String 是不可变的对象, 因此在每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。

使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。

在某些特别情况下, String 对象的字符串拼接其实是被 Java Compiler 编译成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,

String s1 =This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(This is only a”).append(“ simple”).append(“ test”);

生成 String s1 对象的速度并不比 StringBuffer 慢。其实在 Java Compiler 里,自动做了如下转换:

Java Compiler直接把上述第一条语句编译为:java存在常量优化机制,编译时s1已经为This is only a simple test在常量池中查找创建

String s1 =This is only a simple test”;  

所以速度很快。但要注意的是,如果拼接的字符串来自另外的 String 对象的话,Java Compiler 就不会自动转换了,速度也就没那么快了,例如:

String s2 =This is only a”;  
String s3 = “ simple”;  
String s4 = “ test”;  
String s1 = s2 + s3 + s4; 

这时候,Java Compiler 会规规矩矩的按照原来的方式去做,String 的 concatenation(即+)操作利用了 StringBuilder(或StringBuffer)的append 方法实现,此时,对于上述情况,若 s2,s3,s4 采用 String 定义,拼接时需要额外创建一个 StringBuffer(或StringBuilder),之后将StringBuffer 转换为 String,若采用 StringBuffer(或StringBuilder),则不需额外创建 StringBuffer。

5. 使用策略

(1)基本原则:

  • 如果要操作少量的数据,用String ;
  • 单线程操作大量数据,用StringBuilder ;
  • 多线程操作大量数据,用StringBuffer。

(2)不要使用String类的"+"来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则。例如:

String result = "";
for (String s : hugeArray) {
    result = result + s;
}

// 使用StringBuilder
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();

当出现上面的情况时,显然我们要采用第二种方法,因为第一种方法,每次循环都会创建一个String result用于保存结果,除此之外二者基本相同(对于jdk1.5及之后版本)

(3)为了获得更好的性能,在构造 StringBuffer 或 StringBuilder 时应尽可能指定它们的容量。当然,如果你操作的字符串长度(length)不超过 16 个字符就不用了,当不指定容量(capacity)时默认构造一个容量为16的对象。不指定容量会显著降低性能。

(4)StringBuilder 一般使用在方法内部来完成类似 + 功能,因为是线程不安全的,所以用完以后可以丢弃。StringBuffer 主要用在全局变量中。

(5)相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在 StringBuffer 上,并且确定你的模块不会运行在多线程模式下,才可以采用 StringBuilder;否则还是用 StringBuffer。


LeetCode [剑指 Offer 05. 替换空格]

题目:请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

  • 示例 1:

    输入:s = "We are happy."
    输出:"We%20are%20happy."
    

思路

//复制到新对象中
class Solution {
    public String replaceSpace(String s) {
        //不考虑线程安全问题的话可以使用StringBuilder,单线程使用,比较快
        StringBuffer sb = new StringBuffer();
        //遍历字符串
        for(int i = 0;i < s.length();i++){
            //使用 sb 逐个复制 s ,碰到空格则替换,否则直接复制
            if(s.charAt(i) == ' '){
                sb.append("%20");
            }else{
                sb.append(s.charAt(i));
            }
        }
        return sb.toString();
    }
}

LeetCode [151. 反转字符串中的单词]

题目:给你一个字符串 s ,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

**注意:**输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

  • 示例 1:

    输入:s = "the sky is blue"
    输出:"blue is sky the"
    
  • 示例 2:

    输入:s = "  hello world  "
    输出:"world hello"
    解释:反转后的字符串中不能存在前导空格和尾随空格。
    
  • 示例 3:

    输入:s = "a good   example"
    输出:"example good a"
    解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。
    

思路

/*
 * 不使用Java内置方法实现
 * 1.去除首尾以及中间多余空格
 * 2.反转整个字符串
 * 3.反转各个单词
 */
class Solution {
    public String reverseWords(String s) {
        // 1.去除首尾以及中间多余空格
        StringBuilder sb = delSpace(s);
        // 2.反转整个字符串
        reverseString(sb,0,sb.length() - 1);
        // 3.反转各个单词
        reverseWords(sb);
        return sb.toString();
    }
    public StringBuilder delSpace(String s){
        int left = 0;
        int 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 ch = s.charAt(left);
            if(ch != ' '){
                sb.append(ch);
            }else if(sb.charAt(sb.length() - 1) != ' '){
                sb.append(ch);
            }
            left++;
        }
        return sb;
    }
    public void reverseString(StringBuilder sb,int left,int right){
        while(left < right){
            char temp = sb.charAt(left);
            sb.setCharAt(left,sb.charAt(right));
            left++;
            sb.setCharAt(right,temp);
            right--;
        }
    }
    public void reverseWords(StringBuilder sb){
        int len = sb.length();
        int left = 0;
        int right = 0;
        while(left < len){
            // 循环至单词的末尾
            while(right < len && sb.charAt(right) != ' '){
                right++;
            }
            // 翻转单词
            reverseString(sb,left,right - 1);
            // 更新left,去找下一个单词
            left = right + 1;
            right++;
        }
    }
}

LeetCode [剑指 Offer 58 - II. 左旋转字符串]

题目:字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

  • 示例 1:

    输入: s = "abcdefg", k = 2
    输出: "cdefgab"
    
  • 示例 2:

    输入: s = "lrloseumgh", k = 6
    输出: "umghlrlose"
    

思路

//整体反转+局部反转
class Solution {
    public String reverseLeftWords(String s, int n) {
        StringBuilder sb = new StringBuilder(s);
        reverseWords(sb,0,n -1);
        reverseWords(sb,n,s.length() - 1);
        return sb.reverse().toString();
    }
    public void reverseWords(StringBuilder sb,int left,int right){
        while(left < right){
            char temp = sb.charAt(left);
            sb.setCharAt(left++,sb.charAt(right));
            sb.setCharAt(right--,temp);
        }
    }
}
//字符串切片
//应用字符串切片函数,可方便实现左旋转字符串
class Solution {
    public String reverseLeftWords(String s, int n) {
        return s.substring(n,s.length()) + s.substring(0,n);
    }
}
//列表遍历拼接
class Solution {
    public String reverseLeftWords(String s, int n) {
        StringBuilder sb = new StringBuilder();
        for(int i = n;i < n + s.length();i++){
            sb.append(s.charAt(i % s.length()));
        }
        return sb.toString();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值