Day08~344.反转字符串、541. 反转字符串II、剑指Offer 05.替换空格、151.翻转字符串里的单词、剑指Offer58-II.左旋转字符串

99 阅读9分钟

摘要

本文详细介绍了字符串的理论基础,以及如何在Java中使用字符串和字符数组进行操作。此外,文章还提供了多个LeetCode算法题的示例,涵盖了字符串的常见应用场景,如反转、替换和翻转等。

1、字符串理论基础

1.1 概念

在Java中,字符串是一个非常重要的数据类型,用于表示文本数据。字符串是一组字符的序列,可以包含字母、数字、标点符号和其他字符。在Java中,字符串是不可变的,这意味着一旦创建了字符串,就不能更改其内容,任何字符串的修改都会生成一个新的字符串对象。

以下是Java字符串的一些重要概念:

  1. 字符串字面值:在Java中,可以使用双引号(" ")来创建字符串字面值,例如:"Hello, World!"。这些字面值在Java中被视为字符串对象。
  2. 字符串对象:字符串对象是String类的实例,可以使用new关键字创建,例如:String str = new String("Hello")。但通常,直接使用字符串字面值创建字符串对象更为常见。
  3. 字符串连接:Java提供了用于连接字符串的操作符(+)。例如,可以使用"Hello, " + "World!"来连接两个字符串,得到"Hello, World!"。
  4. 字符串方法:String类包含许多用于处理字符串的方法,例如length()方法用于获取字符串的长度,charAt()方法用于获取字符串中特定位置的字符,substring()方法用于提取子字符串,等等。
  5. 不可变性:Java字符串是不可变的,这意味着一旦创建了一个字符串,就不能修改它的内容。任何对字符串的修改操作都会生成一个新的字符串对象。
  6. 字符串池:Java中有一个字符串池,它是一个存储字符串字面值的内存区域。当创建一个字符串字面值时,如果字符串池中已经存在相同内容的字符串,就会重用现有的字符串对象,而不是创建新的对象。
  7. 字符串比较:要比较两个字符串是否相等,应该使用equals()方法而不是==运算符。equals()方法比较两个字符串的内容,而==运算符比较它们的引用地址。
  8. 不可变性的好处:字符串的不可变性使其更安全,因为它们不能在运行时被修改。这对于处理敏感数据非常重要。此外,字符串不可变性还使得字符串可以被缓存,提高了性能。

总之,Java字符串是一种重要的数据类型,具有不可变性和丰富的方法集,可用于处理文本数据。了解字符串的基本概念是Java编程中的重要一步。

1.2 字符串的操作

在Java中,有许多字符串操作可以用来处理和操作字符串。以下是一些常见的Java字符串操作:

  1. 字符串的创建:可以使用字符串字面值(双引号括起来的文本)或使用new关键字创建字符串对象。例如:

    String str1 = "Hello, World!"; // 使用字符串字面值创建
    String str2 = new String("Java"); // 使用new关键字创建
    
  2. 字符串连接:可以使用加号(+)运算符来连接字符串,将两个字符串合并成一个新的字符串:

    String firstName = "John";
    String lastName = "Doe";
    String fullName = firstName + " " + lastName; // 结果为 "John Doe"
    
  3. 字符串长度:使用length()方法可以获取字符串的长度,即包含多少个字符:

    String text = "Hello, World!";
    int length = text.length(); // length 的值为 13
    
  4. 字符串提取:使用substring()方法可以从字符串中提取子字符串。它可以根据起始索引和结束索引提取部分字符串:

    String text = "Hello, World!";
    String subString = text.substring(7, 12); // 结果为 "World"
    
  5. 字符串查找:可以使用indexOf()方法来查找子字符串在原始字符串中的位置。如果找不到,它返回-1:

    String text = "Hello, World!";
    int index = text.indexOf("World"); // index 的值为 7
    
  6. 字符串替换:使用replace()方法可以替换字符串中的特定字符或子字符串:

    String text = "Hello, World!";
    String replaced = text.replace("World", "Java"); // 结果为 "Hello, Java!"
    
  7. 字符串拆分:使用split()方法可以将字符串拆分成字符串数组,根据指定的分隔符进行拆分:

    String text = "apple,banana,cherry";
    String[] fruits = text.split(","); // fruits 包含 ["apple", "banana", "cherry"]
    
  8. 字符串比较:使用equals()方法来比较两个字符串是否相等。如果要忽略大小写,可以使用equalsIgnoreCase()方法:

    String str1 = "Hello";
    String str2 = "hello";
    boolean isEqual = str1.equals(str2); // isEqual 的值为 false
    
  9. 字符串转换:可以使用toLowerCase()toUpperCase()方法将字符串转换为小写或大写:

    String text = "Hello, World!";
    String lowerCase = text.toLowerCase(); // 结果为 "hello, world!"
    String upperCase = text.toUpperCase(); // 结果为 "HELLO, WORLD!"
    
  10. 字符串去除空格:使用trim()方法可以去除字符串两端的空格:

    String text = "   Java Programming   ";
    String trimmed = text.trim(); // 结果为 "Java Programming"
    

这些是Java中常用的字符串操作。了解如何使用这些操作可以更方便地处理和操作字符串数据。

1.3 字符串与char数组互转

在 Java 中,你可以很容易地进行 char 数组和 String 之间的相互转换。以下是两者之间转换的方法:

  1. char 数组转为 String:你可以使用 String 的构造函数来将 char 数组转换为字符串。

    char[] charArray = {'H', 'e', 'l', 'l', 'o'};
    String str = new String(charArray);
    
  2. String 转为 char 数组:你可以使用 toCharArray() 方法将字符串转换为 char 数组。

    String str = "Hello";
    char[] charArray = str.toCharArray();
    

这些方法是非常简单且常见的,可以方便地在 char 数组和字符串之间进行转换。需要注意的是,String 在 Java 中是不可变的,所以从性能和内存角度来看,如果需要频繁地修改字符串内容,StringBuilderStringBuffer 这样的可变字符串类可能更适合。

1.4 StringBuilder的操作

StringBuilder 是 Java 中用于操作可变字符串的类,它提供了一系列方法来进行字符串的拼接、插入、删除等操作,而且由于它的可变性,适合处理需要频繁修改字符串的场景,比如在循环中拼接大量字符串。

以下是一些常用的 StringBuilder 操作:

  1. 创建 StringBuilder:可以使用构造函数来创建一个空的 StringBuilder 对象,也可以传入一个初始字符串:

    StringBuilder sb1 = new StringBuilder(); // 创建一个空的 StringBuilder
    StringBuilder sb2 = new StringBuilder("Hello"); // 创建一个包含初始内容的 StringBuilder
    
  2. 字符串追加:使用 append() 方法可以将字符串添加到 StringBuilder 的末尾:

    StringBuilder sb = new StringBuilder("Hello");
    sb.append(", World!"); // 在末尾追加字符串
    
  3. 字符串插入:使用 insert() 方法可以在指定位置插入字符串:

    StringBuilder sb = new StringBuilder("Hello");
    sb.insert(5, ", World!"); // 在索引 5 处插入字符串
    
  4. 字符串删除:使用 delete() 方法可以删除指定范围内的字符:

    StringBuilder sb = new StringBuilder("Hello, World!");
    sb.delete(5, 12); // 删除索引 5 到 11 的字符
    
  5. 字符串替换:使用 replace() 方法可以替换指定范围内的字符:

    StringBuilder sb = new StringBuilder("Hello, World!");
    sb.replace(7, 13, "Java"); // 将索引 7 到 12 的字符替换为 "Java"
    
  6. 字符串反转:使用 reverse() 方法可以反转 StringBuilder 中的字符顺序:

    StringBuilder sb = new StringBuilder("Hello");
    sb.reverse(); // 反转字符串,结果为 "olleH"
    
  7. 获取字符串长度:使用 length() 方法可以获取 StringBuilder 中字符的数量:

    StringBuilder sb = new StringBuilder("Hello, World!");
    int length = sb.length(); // 获取长度,结果为 13
    
  8. 转换为字符串:使用 toString() 方法可以将 StringBuilder 转换为不可变的字符串:

    StringBuilder sb = new StringBuilder("Hello");
    String str = sb.toString(); // 转换为字符串
    

这些是 StringBuilder 的一些常见操作。它的可变性和高效性使得它在处理字符串拼接等任务时比普通的字符串拼接更加高效。

charAt和setCharAt

StringBuilder 类提供了 charAtsetCharAt 方法来访问和修改字符串中特定位置的字符。

  1. charAt 方法

    • charAt(int index) 方法用于获取字符串中指定索引位置的字符。索引从0开始,即第一个字符的索引为0,第二个字符的索引为1,依此类推。如果索引超出了字符串的范围,将抛出 StringIndexOutOfBoundsException 异常。

    示例:

    StringBuilder sb = new StringBuilder("Hello, World");
    char ch = sb.charAt(7); // 获取索引7处的字符,结果为 'W'
    
  2. setCharAt 方法

    • setCharAt(int index, char ch) 方法用于修改字符串中指定索引位置的字符为指定的字符。如果索引超出了字符串的范围,将抛出 StringIndexOutOfBoundsException 异常。

    示例:

    StringBuilder sb = new StringBuilder("Hello, World");
    sb.setCharAt(7, 'J'); // 将索引7处的字符修改为 'J'
    

请注意,StringBuilder 中的字符索引操作与字符串(String)类似,但由于 StringBuilder 是可变的,因此可以使用 setCharAt 方法来修改字符串的内容。如果需要频繁对字符串进行修改操作,特别是在循环中,StringBuilder 可以提供更好的性能,因为它不会创建大量的临时字符串对象。

2、344.反转字符串

2.1 思路

双指针,left、right分别指向字符串首尾位置,原地交换并逐步向中间移动

2.2 代码

    public void reverseString(char[] s) {
        int left = 0, right = s.length -1;
​
        while(left < right) {
            char temp = s[left];
            s[left] = s[right];
            s[right] = temp;
​
            left++;
            right--;
        }
    }

3、541. 反转字符串II

3.1 思路

首先while循环,定义全部反转的循环次数loop, loop = s.length() / (2*k),按题目中的规则反转 定义剩余字符为last, 若0=<last<k, 全部反转, k=<last<2k,反转前k

3.2 代码

error-1

    public String reverseStr(String s, int k) {
        int count = 2 * k;
        int loop = s.length() / count;
        int last = s.length() % count;
        char[] arr = s.toCharArray();
​
        // 每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符
        int i = 0;
        while(i < loop) {
            int left = i * count;
            int right = left + k;
            reverseString(arr, left, right);
​
            i++;
        }
​
        // 剩余字符反转
        if(0 <= last && last < k) {
            reverseString(arr, i * count, i * count + last);
        } else {
            reverseString(arr, i * count, i * count + k);
        }
        return new String(arr);
    }
​
    public void reverseString(char[] s, int left, int right) {
        while(left < right) {
            char temp = s[left];
            s[left] = s[right];
            s[right] = temp;
​
            left++;
            right--;
        }
    }

right 取值的问题导致错误,应该需要减去 1

  • right = left + k -1
  • right = i * count + last -1
  • right = i * count + k - 1

AC

    public String reverseStr(String s, int k) {
        int count = 2 * k;
        int loop = s.length() / count;
        int last = s.length() % count;
        char[] arr = s.toCharArray();
​
        // 1.每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符
        int i = 0;
        while(i < loop) {
            int left = i * count;
            int right = left + k - 1;
            reverseString(arr, left, right);
​
            i++;
        }
​
        // 2.剩余字符反转
        if(0 <= last && last < k) {
            reverseString(arr, i * count, i * count + last - 1);
        } else {
            reverseString(arr, i * count, i * count + k - 1);
        }
        return new String(arr);
    }
​
    public void reverseString(char[] s, int left, int right) {
        while(left < right) {
            char temp = s[left];
            s[left] = s[right];
            s[right] = temp;
​
            left++;
            right--;
        }
    }

4、剑指Offer 05.替换空格

4.1 思路

使用 StringBuilder,是空格则替换,否则直接复制

4.2 代码

        StringBuilder builder = new StringBuilder();
        for(int i=0; i<s.length(); i++) {
            char ch = s.charAt(i);
            if(ch == ' ') {
                builder.append("%20");
            } else {
               builder.append(ch);
            }
        }
        return builder.toString();

5、151.翻转字符串里的单词

5.1 思路

1.同移除元素,移除字符串的前后和中间的空格 2.反转整个字符串 3.逐个反转单词

  • 移除空格

    • 首先移除前后空格,然后移除中间的空格
    • 复制数组:Arrays.copyOf(arr, newIdx)
  • 逐个反转单词

    • 遍历字符串,如果遍历结束或者遇到空格则反转字符串,更新 left=i+1
    • right = (i != len-1) ? i-1 : len-1; 等价于 right = i-1

5.2 代码

    public String reverseWords(String s) {
        // 1.移除字符串的前后和中间的空格
        char[] arr = removeSpace(s);
        // 2.反转整个字符串
        reverseString(arr, 0, arr.length-1);
        // 3.逐个反转单词
        reverseWord(arr);
        return new String(arr);
    }
​
​
    public char[] removeSpace(String s) {
        char[] arr = s.toCharArray();
        int len = arr.length;
        int left = 0, right = len-1;
        
        // 移除前后的字符串
        while(left < right && arr[left] == ' ') {
            left++;
        }
        while(left < right && arr[right] == ' ') {
            right--;
        }
​
        // 移除中间的字符串
        int newIdx = 0;
        for(int oldIdx=left; oldIdx<=right; oldIdx++) {
            if(arr[oldIdx] != ' ') {
                arr[newIdx] = arr[oldIdx];
                newIdx++;
                continue;                   
            }
​
            // 移除连续的空格,只保留一个空格
            while(oldIdx<=right-1 && arr[oldIdx+1]==' ') {
                oldIdx++;
            }
            arr[newIdx] = ' ';
            newIdx++;
        }
        return Arrays.copyOf(arr, newIdx);
    }
​
    public void reverseWord(char[] arr) {
        int len = arr.length;
​
        int left = 0;
        int right = 0;
        for(int i=0; i<len; i++) {
            // 跳出循环的两种情况:1.遇到空格;2.遍历结束
            if(i == len -1 || arr[i] == ' ') {
                right = (i != len-1) ?  i-1 : len-1;
                reverseString(arr, left, right);
                // 更新left
                left = i+1;
            }
        }
    }
​
    public void reverseString(char[] s, int left, int right) {
        while(left < right) {
            char temp = s[left];
            s[left] = s[right];
            s[right] = temp;
​
            left++;
            right--;
        }
    }

6、剑指Offer58-II.左旋转字符串

6.1 思路

3次反转字符串 第一次:ab -> ba ; bacdefg 第二次:cdefg -> gdedc ; bagdedc 第三次:bagdedc -> bagdedc

6.2 代码

    public String reverseLeftWords(String s, int n) {
        StringBuilder builder = new StringBuilder(s);
​
        reverseString(builder, 0, n-1);
        reverseString(builder, n, s.length()-1);
        reverseString(builder, 0, s.length()-1);
​
        return builder.toString();
    }
​
    public void reverseString(StringBuilder builder, int left, int right) {
        while(left < right) {
            char temp = builder.charAt(left);
            builder.setCharAt(left, builder.charAt(right));
            builder.setCharAt(right, temp);
​
            left++;
            right--;
        }
    } 

参考资料

代码随想录-反转字符串

代码随想录-反转字符串II

代码随想录-替换空格

代码随想录-翻转字符串里的单词

代码随想录-左旋转字符串