将字符串循环左移n个位置

/**
 * @file 020_move_string.c
 * @author dinghuaneng
 * @date 2011.06.22
 * @brief 将字符串进行向左旋转,即循环左移的算法实现。
 *        最后那种方法在时间和空间上都很高效,且代码简短,很难出错。
 *        最节约空间和时间的方法来源:《编程珠玑》
 * @defgroup move_string
 * @{
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*********************** 最节约时间的方法 ************************/
/**
 * @brief 将字符串向左旋转n个位置
 * @param      str  待旋转的字符串
 * @param[in]  mov  需要旋转的数量
 * @return 无
 */
void move_string_left(char *str, int mov)
{
    if (NULL == str || mov <= 0)
        return;
    char tmp[mov];
    int i;
    int len = strlen(str);
    if (len == 0)
        return;
    mov %= len;
    if (mov == 0)
        return;
    for (i = 0; i < sizeof tmp; i++)
        tmp[i] = str[i];
    tmp[i] = '\0';
    for (i = 0; i < len-mov; i++)
        str[i] = str[i+mov];
    for (; i < len; i++)
        str[i] = tmp[i-(len-mov)];
}

/**
 * @brief 将字符串向右旋转n个位置
 * @param      str  待旋转的字符串
 * @param[in]  mov  需要旋转的数量
 * @return 无
 */
void move_string_right(char *str, int mov)
{
    if (NULL == str || mov <= 0)
        return;
    char tmp[mov];
    int i;
    int len = strlen(str);
    if (len == 0)
        return;
    mov %= len;
    if (mov == 0)
        return;
    for (i = len - mov; i < len; i++) {
        tmp[i-(len-mov)] = str[i];
    }
    tmp[i-(len-mov)] = '\0';
    for (i = len - 1; i >= mov; i--) {
        str[i] = str[i-mov];
    }
    for (; i >= 0; i--)
        str[i] = tmp[i];
}
/*********************** 最节约时间的方法 ************************/

/*********************** 最节约空间的方法 ************************/
/**
 * @brief 将字符串向左旋转1个位置
 * @param      str  待旋转的字符串
 * @param[in]  mov  需要旋转的数量
 * @return 无
 */
void move_string_one_left(char *str)
{
    if (NULL == str)
        return;
    int  len = strlen(str);
    int  i;
    if (len == 0)
        return;
    char tmp = str[0];
    for (i=0; i<len-1; i++) {
        str[i] = str[i+1];
    }
    str[i] = tmp;
}

/**
 * @brief 将字符串向右旋转1个位置
 * @param      str  待旋转的字符串
 * @param[in]  mov  需要旋转的数量
 * @return 无
 */
void move_string_one_right(char *str)
{
    if (NULL == str)
        return;
    int  len = strlen(str);
    if (len == 0)
        return;
    char tmp = str[len-1];
    int  i;
    for (i=len-1; i>0; i--) {
        str[i] = str[i-1];
    }
    str[i] = tmp;
}
/*********************** 最节约空间的方法 ************************/

/*********************** 最节约空间和时间的方法之一 ************************/
/**
 * @brief 返回数值i和j的最大公约数
 * @return 正确返回最大公约数,参数有问题返回-1
 */
int gcd(int i, int j)
{
    if (i<=0 || j<=0)
        return -1;
    while (i != j) {
        if (i > j)
            i -= j;
        else
            j -= i;
    }
    return i;
}

/**
 * @brief 将字符串向左旋转n个位置
 * @param      str  待旋转的字符串
 * @param[in]  mov  需要旋转的数量
 * @return 无
 */
void move_string_fast_left(char *str, int mov)
{
    if (NULL == str || mov <= 0)
        return;
    int len = strlen(str);
    char tmp;
    if (!mov)
        return;
    mov %= len;
    if (!mov)
        return;
    int i, j, k;
    int g_cd = gcd(mov, len);
    for (i=0; i<g_cd; i++) {
        tmp = str[i];
        j = i;
        while (1) {
            k = j + mov;
            if (k >= len)
                k -= len;
            if (k == i)
                break;
            str[j] = str[k];
            j = k;
        }
        str[j] = tmp;
    }
}

/**
 * @brief 将字符串向右旋转n个位置
 * @param      str  待旋转的字符串
 * @param[in]  mov  需要旋转的数量
 * @return 无
 */
void move_string_fast_right(char *str, int mov)
{
    if (NULL == str || mov <= 0)
        return;
    int len = strlen(str);
    if (!mov)
        return;
    mov %= len; // 修移动次数
    if (!mov)
        return;
    mov = len - mov;
    move_string_left(str, mov);
}
/*********************** 最节约空间和时间的方法之一 ************************/

/*********************** 最节约空间和时间的方法之二 ************************/
/**
 *  @brief 交换字符串str中的从pos1开始和从pos2开始长度为num的两部分元素。
 *         注意防止内存越界!
 *  @param      str  待交换部分字符的字符串
 *  @param[in]  pos1 第一部分起始位置
 *  @param[in]  pos2 第二部分起始位置
 *  @param[in]  num  要交换的字符数量
 */
void swap_string(char *str, int pos1, int pos2, int num)
{
    char *str1 = str + pos1;
    char *str2 = str + pos2;
    int i;
       char tmp;
    for (i=0; i<num; i++) {
        tmp   = *str1;
        *str1 = *str2;
        *str2 = tmp;
        str1++;
        str2++;
    }
}

/**
 * @brief 用交换元素的方法进行向左旋转(循环左移)
 * @param      str  待旋转的字符串
 * @param[in]  mov  需要旋转的数量
 * @return 无
 */
void move_string_swap_left(char *str, int mov)
{
    if (NULL == str || mov <= 0)
        return;
    int len = strlen(str);
    if (!mov)
        return;
    mov %= len; // 修移动次数
    if (!mov)
        return;
    int i = mov;
    int j = len - mov;
    while (i != j) {
        if (i > j) {
            swap_string(str, mov-i, mov, j);
            i -= j;
        }
        else {
            swap_string(str, mov - i, mov - i + j, i);
            j -= i;
        }
    }
    swap_string(str, mov-i, mov, i);
}
/*********************** 最节约空间和时间的方法之二 ************************/

/*********************** 最节约空间和时间的方法之三 ************************/
/**
 * @brief 将字符串str中从start开始至end结束的字符进行逆转
 * @param     str  待逆转部分字符的字符串
 * @param[in] start  开始的位置
 * @param[in] end    结束的位置
 * @return 无
 */
void reverse(char *str, int start, int end)
{
    char *pos1 = str + start;
    char *pos2 = str + end;
    char  tmp;
    while (pos1 < pos2) {
        tmp   = *pos1;
        *pos1 = *pos2;
        *pos2 = tmp;
        pos1++;
        pos2--;
    }
}

/**
 * @brief  利用逆转的方法对字符串进行向左旋转(循环左移)
 * @param      str  待旋转的字符串
 * @param[in]  mov  需要旋转的数量
 * @return 无
 */
void move_string_reverse_left(char *str, int mov)
{
    if (NULL == str || mov <= 0)
        return;
    int len = strlen(str);
    if (!mov)
        return;
    mov %= len; // 修移动次数
    if (!mov)
        return;
    reverse(str, 0, mov-1);
    reverse(str, mov, len-1);
    reverse(str, 0, len-1);
}
/*********************** 最节约空间和时间的方法之三 ************************/
/** @} */

#if 1
int main(int argc, char **argv)
{
    char str[] = "Hello World!";
    int  n = atoi(argv[1]);
    int  i;
    move_string_reverse_left(str, n);
    printf("%s\n", str);
    return 0;
}
#endif

 

<think>好的,我现在需要解决用户的问题:如何通过编程实现字符串循环左移n个字符。首先,我得理解什么是循环左移。比如,字符串“abcXYZdef”左移3位,结果应该是“XYZdefabc”。也就是说,前三个字符被移到末尾。用户可能需要具体的实现方法或示例代码。 根据用户提供的引用,特别是引用[3]和[4],提到了三次反转的方法。这应该是一个有效的算法,可能比直接切片更高效,尤其是在处理大字符串时。我需要详细解释这个方法的步骤,并给出相应的代码示例。 首先,我需要考虑字符串长度和左移位数k的关系。如果k大于字符串长度n,应该取模运算,即k = k % n,避免不必要的移动。例如,如果n是5,k是7,实际有效移动是2次。 然后,三次反转的步骤: 1. 反转前k个字符。 2. 反转剩下的n-k个字符。 3. 反转整个字符串。 比如,例子中的“abcXYZdef”左移3位: - 原字符串分为前3个“abc”和后6个“XYZdef”。 - 反转前3个得到“cba”。 - 反转后6个得到“fedZYX”。 - 合并后整个字符串是“cbafedZYX”,再整体反转得到“XYZdefabc”,正确结果。 接下来,我需要编写Python函数实现这个逻辑。首先处理k的值,然后分段反转。反转函数可以定义一个辅助函数,或者直接使用字符串切片。Python中字符串是不可变的,所以可能需要转换成列表来处理反转,或者利用切片特性。 比如,引用[4]中的代码使用了reverse_segment函数,但具体实现可能需要用列表操作。例如,将字符串转为列表,反转指定区间,再转回字符串。 另外,用户可能还知道直接切片的方法,比如s[k:] + s[:k]。这种方法更简单,但需要说明其适用性和可能的性能差异。例如,对于非常大的字符串,三次反转可能在内存或时间上更优,但实际中切片方法在Python中可能更高效,因为切片操作很优化。 不过根据引用[3]和[4],三次反转的方法可能更适用于某些特定情况,比如不允许使用额外空间,或者需要在原地操作。但Python字符串不可变,两种方法都需要生成新字符串,所以切片可能更简洁。 因此,可能需要给出两种方法,并比较它们的优缺点。用户可能需要根据具体情况选择。 此外,需要注意k为0或大于字符串长度的情况,比如k=0时直接返回原字符串,或者k等于长度时也是返回原字符串,所以取模处理很重要。 现在,我需要整理这些思路,写出清晰的步骤,并提供代码示例。同时,根据用户提供的引用,正确引用相关的方法,比如三次反转的方法引用[3]和[4],而直接切片可能没有直接引用,但属于常见方法。 可能的问题:用户提供的引用中有没有提到切片的方法?根据引用[4]中的示例代码,使用的是三次反转,而引用[1]的问题可能直接拼接字符串。比如,如果k=3,那么将前三个字符移到后面,可以用s[3:] + s[:3]。这种方法虽然简单,但可能在空间上需要复制字符串两次,但对于一般情况来说足够高效。 所以,在回答中应该提到两种方法:直接切片和三次反转,并解释它们的原理,引用相关的资料。例如,三次反转的方法引用[3]和[4],而直接切片可能属于常见做法,但用户提供的引用中没有明确提到,所以可能不需要引用。 另外,需要注意代码的正确性。例如,三次反转的实现是否正确,是否需要处理k=0的情况,或者在Python中如何处理字符串的反转。例如,反转前k个字符可以通过切片反转,如s[:k][::-1]。 总结步骤: 方法一:直接切片拼接 1. 计算有效左移位数k = k % n 2. 返回s[k:] + s[:k] 方法二:三次反转法 1. 计算有效k 2. 反转前k个字符 3. 反转剩下的字符 4. 反转整个字符串 需要编写这两种方法的代码示例,并解释其原理。 例如,方法一的Python代码: def left_rotate_slice(s, k): n = len(s) if n == 0: return s k %= n return s[k:] + s[:k] 方法二的实现可能需要辅助函数来反转子串,或者利用切片: def reverse_segment(s, start, end): # 将字符串s的start到end部分反转 # 转换为列表处理 lst = list(s) while start < end: lst[start], lst[end] = lst[end], lst[start] start += 1 end -= 1 return ''.join(lst) def left_rotate_reverse(s, k): n = len(s) if n == 0: return s k %= n if k == 0: return s s = reverse_segment(s, 0, k-1) s = reverse_segment(s, k, n-1) s = reverse_segment(s, 0, n-1) return s 或者,利用Python的切片特性,直接分三段反转: def left_rotate_reverse_python(s, k): n = len(s) if n == 0: return s k %= n if k == 0: return s # 反转前k个 part1 = s[:k][::-1] # 反转剩余部分 part2 = s[k:][::-1] # 整体反转 return (part1 + part2)[::-1] 不过这里可能需要测试是否正确。例如,原字符串是abcXYZdef,左移3位: 原始:abc XYZdef 反转前3个:cba XYZdef 反转后部分:cba fedZYX 整体反转:XYZdefabc,正确。 而用切片方法的话,原字符串s[k:] + s[:k]即XYZdef + abc → XYZdefabc,正确。 所以两种方法都正确,但三次反转可能在Python中效率不如直接切片,因为切片操作非常高效,而多次反转可能需要更多操作。 因此,在回答中应该介绍这两种方法,并说明各自的优缺点。例如,直接切片简单直观,适合大多数情况;而三次反转可能在特定场景或语言中有优势,比如当不允许使用额外空间时(但Python字符串不可变,所以都需要创建新字符串)。 此外,用户可能关心特殊情况,比如空字符串,k为0,或者k大于字符串长度,这些都需要处理,所以在代码中需要包含这些情况的判断。 最后,根据用户提供的引用,正确引用三次反转的方法来自引用[3]和[4],而直接切片可能没有引用,但属于常见解决方案。 总结回答的结构: 1. 问题理解:循环左移的定义 2. 方法一:直接切片拼接,步骤和代码 3. 方法二:三次反转法,步骤和代码,引用[3][4] 4. 比较两种方法的优缺点 5. 注意事项:处理k的有效值,空字符串等</think>### 字符串循环左移的编程实现方法 字符串循环左移 $n$ 个字符的操作可以通过以下两种方法实现: --- #### 方法一:直接切片拼接(直观高效) **实现步骤**: 1. 计算有效左移位数 $k = k \% len(s)$,避免无效移动 2. 将字符串分为 $s[k:]$ 和 $s[:k]$ 两部分 3. 拼接后半部分与前半部分 **Python 示例代码**: ```python def left_rotate_slice(s, k): n = len(s) if n == 0: return s k %= n return s[k:] + s[:k] ``` **测试示例**: 输入 `s="abcXYZdef", k=3`,输出 `"XYZdefabc"` --- #### 方法二:三次反转法(数学优化) **实现原理**: 1. 反转前 $k$ 个字符 2. 反转剩余 $n-k$ 个字符 3. 整体反转字符串 通过三次反转操作实现等价位移[^3][^4] **Python 示例代码**: ```python def reverse_segment(s, start, end): lst = list(s) while start < end: lst[start], lst[end] = lst[end], lst[start] start += 1 end -= 1 return ''.join(lst) def left_rotate_reverse(s, k): n = len(s) if n == 0: return s k %= n if k == 0: return s s = reverse_segment(s, 0, k-1) s = reverse_segment(s, k, n-1) s = reverse_segment(s, 0, n-1) return s ``` --- ### 方法对比 | 方法 | 时间复杂度 | 空间复杂度 | 适用场景 | |-----------|--------|--------|--------------| | 切片拼接 | $O(n)$ | $O(n)$ | 通用场景,代码简洁 | | 三次反转法 | $O(n)$ | $O(n)$ | 需原地操作时更优 | --- ### 注意事项 1. **边界处理**:当 $k=0$ 或 $k$ 是字符串长度整数倍时直接返回原字符串 2. **空字符串处理**:输入为空字符串时应直接返回 3. **编码陷阱**:Python 字符串不可变,操作时需转换为列表或生成新字符串
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值