返回

剑指Offer 58:左旋转字符串

Android

揭秘「左旋转字符串」的奇妙技巧

引言

在剑指 Offer 的编程练习中,字符串操作问题层出不穷,「左旋转字符串」就是其中颇具挑战性的一道。这道题看似简单,却蕴含着巧妙的技巧,需要我们深入理解字符串的本质。

任务目标

给定一个字符串 s 和一个正整数 n,我们的目标是将 s 向左旋转 n 个位置。

例如:

  • 输入:s = "abcdefg", n = 2
  • 输出:"cdefgab"

要求:

  • s 由英文字母组成,长度在 1 到 10000 之间。
  • n 是一个正整数,范围为 1 到 10000。

解决思路:巧用字符串拼接和切片

由于 Java 中字符串是不可变类型,因此无法直接修改原字符串。为了解决这个问题,我们可以借助字符串拼接和切片操作。

  1. 复制字符串: 将字符串 s 复制两次,得到新的字符串 t = s + s
  2. 截取子字符串: 从索引 n 开始,截取字符串 t 的子字符串,得到 sub = t.substring(n)
  3. 拼接字符串: 从索引 0 到 n-1,截取字符串 t 的子字符串,得到 pref = t.substring(0, n)
  4. 返回结果:subpref 拼接起来,得到旋转后的字符串:sub + pref

代码实现

public String leftRotateString(String s, int n) {
    if (s == null || s.length() == 0) {
        return "";
    }

    int len = s.length();
    n %= len; // 避免 n 过大

    String t = s + s;
    String sub = t.substring(n);
    String pref = t.substring(0, n);

    return sub + pref;
}

优化技巧:字符数组翻转

为了进一步优化代码性能,我们可以利用 Java 字符数组的特性。

优化后的代码

public String leftRotateString(String s, int n) {
    if (s == null || s.length() == 0) {
        return "";
    }

    int len = s.length();
    n %= len; // 避免 n 过大

    char[] chars = s.toCharArray();
    reverse(chars, 0, len - 1);
    reverse(chars, 0, n - 1);
    reverse(chars, n, len - 1);

    return new String(chars);
}

private void reverse(char[] chars, int start, int end) {
    while (start < end) {
        char temp = chars[start];
        chars[start] = chars[end];
        chars[end] = temp;
        start++;
        end--;
    }
}

优点: 直接操作字符数组,避免了字符串拼接的额外内存开销,大大提升了代码效率。

常见问题解答**

  1. 如何处理 n 大于字符串长度的情况?

    • 代码中使用了取余运算 n %= len,避免 n 过大。这意味着当 n 大于字符串长度时,只旋转字符串的部分长度。
  2. 为什么需要复制字符串两次?

    • 复制字符串是为了避免修改原字符串。因为 Java 中字符串是不可变类型,我们只能通过拼接和切片操作来得到新的字符串。
  3. 字符数组翻转是如何工作的?

    • 字符数组翻转通过交换数组中元素的位置来实现。reverse 方法将数组划分为三个部分,并依次翻转它们。
  4. 为什么字符数组翻转更有效率?

    • 字符数组翻转避免了字符串拼接操作,减少了内存开销和时间复杂度。
  5. 如何确定旋转的次数?

    • 取余运算 n %= len 确保 n 不会超过字符串长度,从而避免重复旋转。

总结**

「左旋转字符串」是一道看似简单却颇具技巧性的题目,考验我们对字符串操作的理解。通过巧用字符串拼接和切片,或者字符数组翻转等优化技巧,我们可以高效地解决这个问题。在实际编码过程中,应根据具体情况选择最优化的实现方式,充分发挥 Java 语言的优势。