Skip to content

2010年 408 数据结构 第 42 题

数据结构2010年综合题10分

题目

设将 n(n > 1)个整数存放到一维数组 R 中。试设计一个在时间和空间两方面都尽可能高效的算法。将 R 中保存的序列循环左移 p(0 < p < n)个位置,即将 R 中的数据由 (x₀, x₁, ..., xₙ₋₁) 变换为 (xₚ, xₚ₊₁, ..., xₙ₋₁, x₀, x₁, ..., xₚ₋₁)。要求:

(1) 给出算法的基本设计思想。

(2) 根据设计思想,采用 C 或 C++ 或 Java 语言描述,关键之处给出注释。

(3) 说明你所设计算法的时间复杂度和空间复杂度。

解析

(1) 设计思想

答案:三次逆置法——先把 R[0..p-1] 整段逆置一次,再把 R[p..n-1] 整段逆置一次,最后把整个 R[0..n-1] 逆置一次。完成后 R 就是循环左移 p 位的结果。

为什么这三步等价于循环左移?

记原序列为 ,目标

拆成两段 ,目标就是把 变成 。三次逆置:

步骤操作结果
起始
1:逆置 R[0..p-1]
2:逆置 R[p..n-1]
3:逆置整个 R[0..n-1]

数学事实:两段拼接后整体逆置 = 各段先单独逆置再交换顺序。三次逆置的本质就是把这条恒等式拆成三个 in-place 操作。

举例验证:n=5、p=2、R = [1, 2, 3, 4, 5],目标 [3, 4, 5, 1, 2]。

操作R 状态
[1, 2, 3, 4, 5]
1逆置 R[0..1][2, 1, 3, 4, 5]
2逆置 R[2..4][2, 1, 5, 4, 3]
3逆置 R[0..4][3, 4, 5, 1, 2] ✓

(2) 代码实现

c
// 原地逆置 R[low..high]
static void reverse(int R[], int low, int high) {
    while (low < high) {
        int t = R[low];
        R[low] = R[high];
        R[high] = t;
        low++;
        high--;
    }
}

void leftShift(int R[], int n, int p) {
    if (p <= 0 || p >= n) return;       // 边界保护:p 越界则不变
    reverse(R, 0,     p - 1);           // 第 1 段逆置
    reverse(R, p,     n - 1);           // 第 2 段逆置
    reverse(R, 0,     n - 1);           // 整体逆置
}

关键点说明

  • 三次逆置是经典 in-place 招数:避免开辅助数组、避免复杂的"环式置换"——三次双指针扫描搞定。
  • 每次 reverse 用 while + 两端向中间靠拢:常数次比较与交换,平均每段 O(段长 / 2)。
  • 边界保护:题面说 0 < p < n 已经排除了平凡情况,工程代码里仍然加 if 防御不亏。
  • 不要分开存"前 p 个"再拼接int *tmp = malloc(p * sizeof(int)); memcpy(...) 这种解法对,但空间 O(p) 不是题目要求的"两方面都尽可能高效"。三次逆置 O(1) 空间、O(n) 时间,是公认的最优解。

(3) 复杂度分析

  • 时间复杂度 :三次逆置每次扫描各自的段,总元素访问数 = ,线性。
  • 空间复杂度 :除入参 R 外,只用 t、low、high 三个标量,与 n 无关。

编者注(生僻术语):本题的三次逆置法又叫「反转法 / 翻转法」,是经典字符串/数组操作技巧。同样的思想可以用来解:(a) 字符串原地反转单词顺序("hello world" → "world hello");(b) 任意循环移位(左移 p 等价于右移 n-p,三次逆置法适用)。考研只考左移,但理解后能举一反三。

最后更新:

🎬 可视化演示
加载中...

提示:可在可视化区直接操作播放、步进、修改参数