这道题目要求我们找出两个有序数组中最接近的两个元素之间的最小距离。这是一个典型的双指针算法应用场景,也是算法竞赛中常见的题型。我们先来理解题目要求:
给定两个数组A和B,分别包含n和m个元素。我们需要找到a∈A和b∈B,使得|a-b|最小。题目保证数组元素在长整型范围内,且测试用例数量t≤100。
最直观的解法是暴力枚举所有可能的(a,b)组合,计算它们的绝对差并记录最小值。这种方法的时间复杂度是O(n×m),当n和m较大时(比如都达到1e5),这种解法显然会超时。
更高效的解法是利用数组已经有序的特性。我们可以先对两个数组分别排序(如果未排序),然后使用双指针技术在线性时间内找到最小距离。这正是题目代码采用的解法。
双指针算法的核心思想是:利用数组的有序性,通过比较两个指针所指元素的大小关系,决定移动哪个指针。具体到本题:
这种方法的时间复杂度主要来自排序步骤(O(n log n + m log m)),而双指针遍历部分只需O(n+m)时间,整体效率显著优于暴力解法。
让我们逐部分解析题目给出的C语言实现代码:
c复制#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
int cmp(const void* x, const void* y) {
return *(long long*)x - *(long long*)y;
}
这里包含了必要的头文件:
比较函数cmp用于qsort排序,它接收两个void指针参数,将其转换为long long指针后解引用比较。返回值为正表示x>y,负表示x<y,0表示相等。
c复制int main() {
int t;
scanf("%d", &t);
while(t--) {
// 处理每个测试用例
}
return 0;
}
程序首先读取测试用例数量t,然后通过while循环处理每个测试用例。这是算法竞赛题的典型处理方式。
c复制long long a[100005] = {0};
long long b[100005] = {0};
long long n, m;
scanf("%lld %lld", &n, &m);
for(int i = 0; i < n; i++) {
scanf("%lld", &a[i]);
}
for(int i = 0; i < m; i++) {
scanf("%lld", &b[i]);
}
qsort(a, n, sizeof(long long), cmp);
qsort(b, m, sizeof(long long), cmp);
这里定义了两个足够大的数组a和b(题目保证n,m≤1e5),分别存储两个输入序列。使用qsort对两个数组进行升序排序,这是双指针算法能够正确工作的前提。
c复制long long min = 1000000000;
long long i = 0, j = 0;
while(i < n && j < m) {
long long diff = llabs(a[i] - b[j]);
if(diff < min) {
min = diff;
}
if(min == 0) {
break;
}
if(a[i] < b[j]) {
i++;
} else {
j++;
}
}
printf("%lld\n", min);
这是算法的核心部分:
为什么双指针法能找到最小距离?关键在于数组的有序性。考虑以下情况:
假设当前A[i] < B[j],那么对于所有k>j,有B[k] ≥ B[j],因此|A[i]-B[k]| ≥ |A[i]-B[j]|。这意味着移动j指针不会得到更小的差值,而移动i指针可能找到更接近B[j]的A元素。
同理,当A[i] > B[j]时,移动i指针不会改善结果,应该移动j指针。当A[i] == B[j]时,已经找到最小可能距离0,可以直接终止。
让我们分析算法的时间复杂度:
因此总时间复杂度为O(n log n + m log m),这在n,m≤1e5时是完全可行的(通常竞赛题要求时间复杂度在1e8以内)。
空间复杂度主要是存储两个数组,为O(n+m)。
在实际编码和竞赛中,有几个关键点需要注意:
题目没有明确说明数据范围,但使用了long long(64位整数)来存储数组元素和结果,这是比较保险的做法。使用int可能会导致溢出。
代码中将min初始化为1e9,这假设了输入数据的绝对值不会超过1e9。更安全的做法是初始化为LLONG_MAX(需要包含limits.h):
c复制#include <limits.h>
long long min = LLONG_MAX;
当发现min==0时直接break是正确的优化,因为0是最小可能距离。这在存在相同元素时能显著提高效率。
题目没有给出n,m的上限,代码中预设了100005的大小。在实际竞赛中,通常:
双指针法有很多变种和应用场景,理解这个基础问题有助于解决更复杂的问题:
可以修改算法来寻找所有距离等于某个特定值的元素对,或者距离小于/大于某个阈值的元素对数量。
如果有多个有序序列,需要找到各序列中各取一个元素的最小极差(最大-最小),可以使用类似的思路配合堆(优先队列)来实现。
这是最近邻搜索问题的一维特例。在高维情况下,可以使用KD树等数据结构来高效解决。
这种算法在实际中有多种应用:
在实现这类算法时,新手常犯的错误包括:
调试时可以:
虽然当前实现已经足够高效,但仍有优化空间:
理解算法后,可以轻松用其他语言实现。例如Python版本:
python复制def min_distance():
import sys
input = sys.stdin.read
data = input().split()
idx = 0
t = int(data[idx])
idx += 1
for _ in range(t):
n, m = map(int, data[idx:idx+2])
idx +=2
a = list(map(int, data[idx:idx+n]))
idx +=n
b = list(map(int, data[idx:idx+m]))
idx +=m
a.sort()
b.sort()
i = j = 0
min_diff = float('inf')
while i < n and j < m:
diff = abs(a[i] - b[j])
if diff < min_diff:
min_diff = diff
if min_diff == 0:
break
if a[i] < b[j]:
i += 1
else:
j += 1
print(min_diff)
Python版本需要注意:
为了验证代码正确性,应该设计各种边界情况的测试用例:
常规情况:
code复制1
3 3
1 3 5
2 4 6
预期输出:1(|3-2|或|5-6|)
包含相同元素:
code复制1
2 2
1 5
5 9
预期输出:0(有相同的5)
一个数组远大于另一个:
code复制1
1 5
100
90 92 95 99 101
预期输出:1(|100-99|或|100-101|)
极值测试:
code复制1
2 2
-1000000000 1000000000
-1000000000 1000000000
预期输出:0
在算法竞赛中解决此类问题时:
要深入理解双指针算法和相关主题,可以参考:
在实际编码和竞赛中,我发现这类问题有几个关键点:
对于这道题,我最初曾犯过没有初始化最小值的错误,导致在某些情况下得到错误结果。后来通过添加断言和测试用例发现了这个问题。这也提醒我,在竞赛中即使看似简单的题目,也要仔细检查所有边界条件。