PTA(顺序表)1-2 数组元素的区间删除

1-2 数组元素的区间删除

分数 5

作者 DS课程组

单位 浙江大学

给定一个顺序存储的线性表,请设计一个函数删除所有值大于min而且小于max的元素。删除后表中剩余元素保持顺序存储,并且相对位置不能改变。

函数接口定义:

1
int Delete( int A[], int L, int minA, int maxA );

其中A是整型数组,存储原始线性表的元素;L是表长,即A中元素的个数;minAmaxA分别为待删除元素的值域的下、上界。函数Delete应将A中所有值大于minA而且小于maxA的元素删除,同时保证表中剩余元素保持顺序存储,并且相对位置不变,最后返回删除后的表长。

裁判测试程序样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

#define MAXN 20

int Delete( int A[], int L, int minA, int maxA );

int main()
{
int A[MAXN], L, minA, maxA, i;

scanf("%d", &L);
for (i=0; i<L; i++) scanf("%d", &A[i]);
scanf("%d %d", &minA, &maxA);
L = Delete(A, L, minA, maxA);
for (i=0; i<L; i++) printf("%d ", A[i]);
printf("\n");

return 0;
}

/* 你的代码将被嵌在这里 */

输入样例:

1
2
3
10
4 -8 2 12 1 5 9 3 3 10
0 4

输出样例:

1
4 -8 12 5 9 10 

答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int Delete( int A[], int L, int minA, int maxA ) {
int i = 0;
while (i != L) {
if (A[i] > minA && A[i] < maxA) {
// 删除A[i]
for (int j = i; j < L-1; j++) {
A[j] = A[j+1];
}
L--;
} else { // 往后看下一位是不是符合要删的条件
i++;
}
}
return L;
}

太妙了:

  • 如果符合要删的,就删掉,下一位要看的往前了,i不变
  • 如果不符合要删的,就i++,看下一位

妙在用 “原位覆盖 + 单次遍历” 的极简逻辑,完美平衡了 “正确性、效率、空间复杂度”,没有多余操作,精准解决了 “删除区间元素 + 保持顺序” 的核心需求。具体妙点可拆解为 3 个维度:

一、逻辑极简:用 “遍历 + 条件保留” 替代复杂删除,思路直观

普通初学者可能会先标记所有要删除的元素,再统一移动剩余元素,或用额外数组存储保留元素 —— 但这个答案直接在原数组上操作,核心逻辑只有 “一句话”:

“遍历数组,遇到要删除的元素就用后续元素覆盖它(相当于删除),遇到要保留的元素就往后走”

代码里的 while (i != L) 循环是核心:

  • A[i] 是 “要删除的(minA <= A[i] <= maxA)”:用 j 循环把 i 后面的元素往前挪 1 位(覆盖 A[i]),同时表长 L--(因为删了一个元素),且 i 不递增(因为覆盖后 A[i] 变成了原 A[i+1],需要重新判断这个新元素)。
  • A[i] 是 “要保留的”:i++ 继续判断下一个元素。

整个过程像 “筛选” 一样,把不需要的元素直接 “过滤掉”,保留的元素自然维持原相对顺序,逻辑非常直接,没有多余步骤。

二、空间最优:原地操作,不占额外空间

很多人会想到用一个 “临时数组” 存储所有要保留的元素,最后再把临时数组复制回原数组 —— 这种方法需要额外开辟 O(L) 的空间(比如 int temp[MAXN])。

而这个答案完全在原数组 A 上操作,没有定义任何额外数组或大变量,空间复杂度是 O(1)(仅用了 ij 两个循环变量),完美贴合 “顺序存储线性表” 的原地操作特性,也符合题目隐含的 “空间效率” 要求。

三、效率合理:避免重复遍历,时间复杂度可控

虽然代码里有两层循环(外层 while + 内层 for),但本质上每个元素最多被 “移动” 一次

  • 要删除的元素会被后面的元素覆盖,覆盖后就不会再被处理;
  • 要保留的元素只会被 “判断一次”,不会被重复移动。

比如输入样例中,要删除的元素是 2、1、3、3,每个被删除元素后面的元素只需要往前挪 1 次,整个过程的总操作次数和 “元素总数” 成正比,实际时间复杂度接近 O(L)(比 “先标记再统一删除” 的两次遍历更高效)。

对于题目中 L 最大为 20MAXN=20)的规模,这种效率完全足够,且代码比 “双指针优化版” 更简洁(双指针虽能做到严格 O(L),但需要额外维护 “保留位置指针”,对初学者来说不如这个答案直观)。

总结:妙在 “贴合问题规模的最优解”

这个答案没有追求 “极致的时间复杂度”(比如用双指针),也没有用 “简单但费空间的临时数组”,而是选择了最适合本题场景的实现方式

  • 逻辑上,初学者能一眼看懂 “删元素就往前挪” 的直观操作;
  • 代码上,行数极少(核心逻辑 10 行内),没有冗余变量;
  • 性能上,完全满足 MAXN=20 的规模需求,且原地操作无额外空间消耗。