C语言学习笔记20260627:字符串左旋的四种解法

C语言学习笔记20260627:字符串左旋的四种解法

一、学习目标

通过经典的"字符串左旋"问题,掌握处理字符串变换类问题的四种核心算法思想。深入理解暴力移位法的底层逻辑,学习分段拷贝与指针截取的库函数技巧,并探究"三次反转法"这一原地算法的数学之美,体会从"模拟操作"到"空间换时间"再到"极致优化"的思维跃迁。

二、问题拆解与核心逻辑

本题要求实现一个函数,将字符串中的前 K 个字符整体挪到字符串末尾。例如字符串 abcdef,左旋 2 位变为 cdefab。核心约束条件为:

  1. 周期性 :如果左旋次数 K 大于字符串长度 len,实际有效旋转次数为 K % len
  2. 空间与效率:需要在时间复杂度与空间复杂度之间做出权衡,部分解法允许使用额外空间,部分解法要求原地(In-place)操作。

三、方法一:暴力移位法(逐位模拟)

3.1 核心思路

将左旋 K 位拆解为"左旋 1 位"的重复操作。单次左旋 1 位的逻辑是:将首字符暂存,将剩余字符整体向前移动一位,最后将暂存的首字符放到末尾。重复此过程 K 次即可。

3.2 完整代码实现

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

// 单次左旋1位
void leftRotateOne(char str[], int len)
{
    char temp = str[0];
    // 整体左移一位
    for (int i = 0; i < len - 1; i++)
    {
        str[i] = str[i + 1];
    }
    str[len - 1] = temp;
}

// 左旋k位
void leftRotateK(char str[], int k)
{
    int len = strlen(str);
    if (len == 0) return;
    k = k % len; // k超过长度取模
    for (int i = 0; i < k; i++)
    {
        leftRotateOne(str, len);
    }
}

int main()
{
    char arr[] = "abcdef";
    int k = 2;
    leftRotateK(arr, k);
    printf("%s\n", arr); // 输出 cdefab
    return 0;
}

3.3 方法优缺点分析

  • 优点:逻辑极其直观,完全模拟了物理上的"挪动"过程,不需要任何额外的数组空间。
  • 缺点:时间复杂度为 O(K × N)。当字符串长度 N 很大且 K 接近 N 时,需要进行大量的字符移动,效率极低。

四、方法二:分段拷贝法(空间换时间)

4.1 核心思想

左旋 K 位本质上就是将字符串从第 K 个位置"切开",把后半段放到前面,前半段放到后面。我们可以申请一个临时数组,按顺序先拷贝后半段,再拷贝前半段,最后一次性拷回原数组。

4.2 完整代码实现

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

void leftRotateK(char str[], int k)
{
    int len = strlen(str);
    if (len == 0) return;
    k = k % len;
    char tmp[1000] = { 0 }; // 临时数组,需保证足够大
    int idx = 0;
    
    // 1. 先拷贝后半段 str[k] ~ str[len-1]
    for (int i = k; i < len; i++)
        tmp[idx++] = str[i];

    // 2. 再拷贝前k个字符
    for (int i = 0; i < k; i++)
        tmp[idx++] = str[i];

    // 3. 复制回原字符串
    strcpy(str, tmp);
}

int main()
{
    char arr[] = "abcdef";
    leftRotateK(arr, 2);
    printf("%s\n", arr);
    return 0;
}

4.3 核心细节解析

  • 该方法将时间复杂度降低到了 O(N),因为每个字符只被拷贝了一次。
  • 缺点:需要消耗 O(N) 的额外空间(临时数组 tmp)。在内存受限的嵌入式开发中需谨慎使用。

五、方法三:三次反转法(原地算法)

5.1 核心思想:数学与逻辑的巧妙结合

这是解决字符串旋转最经典的原地算法。以 abcdef 左旋 2 位为例:

  1. 翻转前 k 个字符:abcdef -> bacdef
  2. 翻转剩余字符:bacdef -> bafedc
  3. 翻转整个字符串:bafedc -> cdefab
    通过三次局部与整体的翻转,完美实现了旋转,且不需要任何额外空间。

5.2 完整代码实现

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

// 翻转字符串 [left,right] 区间
void reverse(char str[], int left, int right)
{
    while (left < right)
    {
        char t = str[left];
        str[left] = str[right];
        str[right] = t;
        left++;
        right--;
    }
}

void leftRotateK(char str[], int k)
{
    int len = strlen(str);
    if (len == 0) return;
    k = k % len;
    reverse(str, 0, k - 1);    // 翻转前k个
    reverse(str, k, len - 1);  // 翻转后半段
    reverse(str, 0, len - 1);  // 整体翻转
}

int main()
{
    char arr[] = "abcdef";
    leftRotateK(arr, 2);
    printf("%s\n", arr);
    return 0;
}

5.3 方法优缺点分析

  • 优点:时间复杂度为 O(N),空间复杂度为 O(1)。既高效又节省内存,是面试与工程实践中的首选解法。
  • 难点:需要较强的逻辑推导能力,初次接触较难想到这种"以翻转代移动"的巧妙思路。

六、方法四:指针 / 字符串截取法(库函数巧用)

6.1 核心思想

利用 C 语言标准库中的字符串处理函数(strcpy, strncat)以及指针运算,极大地简化代码编写量。核心逻辑依然是"先取后半段,再拼前半段"。

6.2 完整代码实现

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

void leftRotateK(char str[], int k)
{
    int len = strlen(str);
    k = k % len;
    char tmp[1000]; // 临时缓冲区

    // 1. 利用指针运算,直接复制后半段(从 str+k 开始到结束)
    strcpy(tmp, str + k);

    // 2. 利用 strncat 拼接前k个字符
    strncat(tmp, str, k);
    
    // 3. 复制回原字符串
    strcpy(str, tmp);
}

int main()
{
    char arr[] = "abcdef";
    leftRotateK(arr, 2);
    printf("%s\n", arr);
    return 0;
}

6.3 核心细节解析

  • 指针运算str + k 直接定位到了需要左旋的分割点,省去了手动写循环拷贝后半段的代码。
  • 库函数优势strncat 能够自动在拼接后添加字符串结束符 \0,避免了手动管理字符串结尾的繁琐与潜在错误。

七、总结

总结

暴力移位法是理解字符串底层操作的基石;分段拷贝与指针截取法展示了标准库函数在提升开发效率上的巨大优势;而三次反转法则体现了算法在时间与空间双重约束下的极致优化。在实际开发中,若对性能要求不高且追求代码可读性,推荐使用库函数法;若在嵌入式底层或算法面试中,三次反转法则是展现扎实功底的最佳选择。