1 题目
给定 m 个数组,每个数组都已经按照升序排好序了。
现在你需要从两个不同的数组中选择两个整数(每个数组选一个)并且计算它们的距离。两个整数 a 和 b 之间的距离定义为它们差的绝对值 |a-b| 。
返回最大距离。
示例 1:
输入:[[1,2,3],[4,5],[1,2,3]]
输出:4
解释:
一种得到答案 4 的方法是从第一个数组或者第三个数组中选择 1,同时从第二个数组中选择 5 。
示例 2:
输入:arrays = [[1],[1]]
输出:0
提示:
m == arrays.length2 <= m <= 1051 <= arrays[i].length <= 500-104 <= arrays[i][j] <= 104arrays[i]以 升序 排序。- 所有数组中最多有
105个整数。
2 代码实现
cpp
class Solution {
public:
int maxDistance(vector<vector<int>>& arrays) {
int res = 0 ;
int min_val = arrays[0][0];
int max_val = arrays[0].back();
for (int i = 1 ; i < arrays.size() ; ++i){
int curr_min = arrays[i][0];
int curr_max = arrays[i].back();
res = max(res,abs(curr_max - min_val));
res = max(res,abs(max_val - curr_min));
min_val = min (min_val , curr_min);
max_val = max (max_val , curr_max);
}
return res;
}
};
乱七八糟的想
...之前的题都做了,以后再做这个题还是一点头绪也没有,最大距离不就是找最大数和最小数的差,最小好说,就是一边遍历一边更新minNum , 最大呢?每一个数组元素的大小又不是一样固定的怎么防止越界。(每次都取一遍size() 吧?)
我特别理解你的困惑 ------ 一开始会觉得 "找最大最小差" 很直观,但卡就卡在「最大 / 最小可能来自同一个数组」这个坑上,还有对 "数组首尾元素" 的利用不熟练。我用更通俗的方式拆解,把你纠结的点逐个说透:
先解决你最核心的疑问:为什么只看数组首尾?
你提到 "每一个数组元素的大小又不是一样固定的怎么防止越界",其实题目里明确说每个数组都是升序排列,这是关键!
- 升序数组的最小值一定在第一个位置(索引 0) ,不可能在中间或最后(比如
[1,2,3],最小只能是 1); - 升序数组的最大值一定在最后一个位置(索引 - 1) ,不可能在中间或开头(比如
[4,5],最大只能是 5);所以根本不用遍历数组内部元素,只看arr[0](最小)和arr[-1](最大)就够了,完全不会 "越界"------ 因为题目保证每个数组长度≥1,arr[0]和arr[-1]一定存在。
再解决:为什么不能只记全局最大 / 最小?
你一开始想的 "一边遍历更 minNum" 是对的,但只记一个 minNum 和一个 maxNum 会踩坑:比如假设有数组[[1, 100], [2, 3]]:
- 全局最小是 1(第一个数组),全局最大是 100(也是第一个数组);
- 如果直接算 100-1=99,就错了!因为题目要求 "两个不同数组选数",1 和 100 来自同一个数组,不能选;
- 正确的最大距离应该是 100-2=98(第一个数组的最大 + 第二个数组的最小),或者 3-1=2(第二个数组的最大 + 第一个数组的最小),取 98。
所以核心问题不是 "找最大 / 最小",而是 "找来自不同数组的最大和最小"------ 这就是为什么我们要记「最小 + 次小」「最大 + 次大」:
- 如果全局最小和全局最大来自不同数组,直接用它们的差(最优解);
- 如果来自同一个数组,就用 "全局最小 + 次大" 或 "全局最大 + 次小",选更大的那个。
用你的思路重写(更贴近你的直觉)
你说 "最小好说,一边遍历一边更新 minNum",那我们就先按这个思路走,再补全 "防同数组" 的逻辑:
python
def maxDistance(arrays):
# 第一步:遍历所有数组,记录每个数组的min(arr[0])和max(arr[-1])
# 同时记录全局最小、全局最大,以及它们的数组索引
global_min = float('inf')
global_min_idx = -1
global_max = float('-inf')
global_max_idx = -1
# 先找全局最小和全局最大(带索引)
for i in range(len(arrays)):
curr_min = arrays[i][0]
curr_max = arrays[i][-1]
# 更新全局最小
if curr_min < global_min:
global_min = curr_min
global_min_idx = i
# 更新全局最大
if curr_max > global_max:
global_max = curr_max
global_max_idx = i
# 第二步:判断是否来自不同数组
if global_min_idx != global_max_idx:
return global_max - global_min
# 第三步:如果来自同一个数组,需要找次小/次大
else:
# 找次小值(排除全局最小所在的数组)
second_min = float('inf')
for i in range(len(arrays)):
if i == global_min_idx:
continue
if arrays[i][0] < second_min:
second_min = arrays[i][0]
# 找次大值(排除全局最大所在的数组)
second_max = float('-inf')
for i in range(len(arrays)):
if i == global_max_idx:
continue
if arrays[i][-1] > second_max:
second_max = arrays[i][-1]
# 取两种情况的最大值
return max(global_max - second_min, second_max - global_min)
这个版本更贴近你的初始思路:
- 先按你想的 "遍历更 minNum/maxNum",同时记索引;
- 发现同数组后,再遍历一次找 "排除该数组后的次小 / 次大";
- 最后比较两种情况。
虽然比之前的版本多了一次遍历,但逻辑更直白,完全对应你 "先找最小 / 最大,再处理异常" 的思考路径。
总结你纠结的点
| 你的疑问 | 核心答案 |
|---|---|
| 为什么只看 arr [0]/arr [-1]? | 数组升序,首尾就是每个数组的最小 / 最大,不用看中间元素 |
| 怎么防止越界? | 题目保证数组长度≥1,arr [0] 和 arr [-1] 一定存在,不会越界 |
| 只记 min/max 为什么不行? | 可能来自同一个数组,违反 "不同数组选数" 的规则 |
| 怎么处理同数组的情况? | 找 "排除该数组后的次小 / 次大",再计算差值 |
其实你一开始的思路已经对了 80%,只差最后一步 "处理同数组的异常情况"。这类题的关键就是:先抓核心规律(升序数组首尾是极值),再补全边界条件(不同数组)。
你可以把这个思路套到之前做过的题里对比:
-
- 买卖股票:核心是 "低买高卖",边界是 "买在卖之前";
-
- 最大增量:核心是 "后数 - 前数最大",边界是 "后数 > 前数";
- 本题:核心是 "最大 - 最小",边界是 "最大最小来自不同数组"。
本质都是 "先找核心解,再修边界",掌握这个逻辑,这类题就通了。
题解
先明确核心前提(题目给的关键条件)
题目里说:每个数组都是升序排列的 → 这意味着:
- 任意一个数组的最小值一定是第一个元素 (
arrays[i][0]); - 任意一个数组的最大值一定是最后一个元素 (
arrays[i].back()); - 我们要找的 "最大距离",本质是「一个数组的最大值 - 另一个数组的最小值」(因为差的绝对值最大,正数直接减就行)。
代码逐行拆解(带示例:输入[[1,2,3],[4,5],[1,2,3]])
我们以示例 1 的输入为例,一步步走代码流程,先贴完整代码 + 标注行号:
cpp
class Solution {
public:
int maxDistance(vector<vector<int>>& arrays) {
// 行1:初始化最终结果为0(最大距离初始为0)
int res = 0 ;
// 行2:初始化全局最小值为第一个数组的最小值(arrays[0][0]=1)
int min_val = arrays[0][0];
// 行3:初始化全局最大值为第一个数组的最大值(arrays[0].back()=3)
int max_val = arrays[0].back();
// 行4:从第二个数组开始遍历(i=1,因为第一个数组已经作为基准了)
for (int i = 1 ; i < arrays.size() ; ++i){
// 行5:当前数组的最小值(比如i=1时,arrays[1][0]=4)
int curr_min = arrays[i][0];
// 行6:当前数组的最大值(比如i=1时,arrays[1].back()=5)
int curr_max = arrays[i].back();
// 行7:计算「当前数组最大值 - 之前所有数组的最小值」,更新最大距离
res = max(res,abs(curr_max - min_val));
// 行8:计算「之前所有数组的最大值 - 当前数组的最小值」,更新最大距离
res = max(res,abs(max_val - curr_min));
// 行9:更新全局最小值(把当前数组的最小值加入比较)
min_val = min (min_val , curr_min);
// 行10:更新全局最大值(把当前数组的最大值加入比较)
max_val = max (max_val , curr_max);
}
// 行11:返回最终的最大距离
return res;
}
};
逐行 + 逐轮遍历解释(示例输入:[[1,2,3],[4,5],[1,2,3]])
第一步:初始化(行 1-3)
res = 0:还没计算任何距离,先把结果设为 0;min_val = arrays[0][0] = 1:第一个数组[1,2,3]的最小值是 1;max_val = arrays[0].back() = 3:第一个数组[1,2,3]的最大值是 3;
第二步:第一次循环(i=1,对应第二个数组[4,5])
-
行 5:
curr_min = arrays[1][0] = 4(第二个数组的最小值); -
行 6:
curr_max = arrays[1].back() = 5(第二个数组的最大值); -
行 7:计算
abs(5 - 1) = 4→ 现在res是max(0,4)=4; -
✨ 含义:第二个数组的最大值(5) - 第一个数组的最小值(1),这是一个候选的最大距离;
-
行 8:计算
abs(3 - 4) = 1→ 现在res是max(4,1)=4; -
✨ 含义:第一个数组的最大值(3) - 第二个数组的最小值(4),这个距离更小,不更新;
-
行 9:更新
min_val = min(1,4) = 1(当前全局最小值还是 1); -
行 10:更新
max_val = max(3,5) = 5(当前全局最大值变成 5); -
→ 本轮结束后:
res=4,min_val=1,max_val=5。
第三步:第二次循环(i=2,对应第三个数组[1,2,3])
-
行 5:
curr_min = arrays[2][0] = 1(第三个数组的最小值); -
行 6:
curr_max = arrays[2].back() = 3(第三个数组的最大值); -
行 7:计算
abs(3 - 1) = 2→res是max(4,2)=4; -
✨ 含义:第三个数组的最大值(3) - 前两个数组的最小值(1),距离 2,比当前 res 小;
-
行 8:计算
abs(5 - 1) = 4→res是max(4,4)=4; -
✨ 含义:前两个数组的最大值(5) - 第三个数组的最小值(1),距离 4,和当前 res 相等;
-
行 9:更新
min_val = min(1,1) = 1(全局最小值还是 1); -
行 10:更新
max_val = max(5,3) = 5(全局最大值还是 5); -
→ 本轮结束后:
res=4,min_val=1,max_val=5。
第四步:返回结果(行 11)
循环结束,返回res=4,和示例的正确输出一致!
再用 "极值同数组" 的例子验证(输入[[1,100],[2,3],[4,5]])
初始化:
res=0,min_val=1(第一个数组最小值),max_val=100(第一个数组最大值);
第一次循环(i=1,数组[2,3]):
curr_min=2,curr_max=3;- 行 7:
abs(3-1)=2→res=2; - 行 8:
abs(100-2)=98→res=98; - 行 9:
min_val=min(1,2)=1; - 行 10:
max_val=max(100,3)=100;
第二次循环(i=2,数组[4,5]):
curr_min=4,curr_max=5;- 行 7:
abs(5-1)=4→res=max(98,4)=98; - 行 8:
abs(100-4)=96→res=max(98,96)=98; - 行 9:
min_val=1; - 行 10:
max_val=100;
返回结果:98(正确,因为最大距离是 100-2=98)。
核心逻辑的 "底层原理"(为什么这样写能解决问题)
-
为什么从 i=1 开始遍历? 第一个数组作为 "基准",后续每个数组只和 "前面所有数组" 比较,天然避免了 "同一个数组选数" 的坑(比如不会出现 "100-1" 这种同数组的情况)。
-
**为什么要算行 7 和行 8 两种情况?**最大距离只有两种可能:
- 情况 1:当前数组的最大值,减去 "前面所有数组" 的最小值(比如 5-1=4);
- 情况 2:"前面所有数组" 的最大值,减去当前数组的最小值(比如 100-2=98);这两种情况覆盖了 "跨数组找最大距离" 的所有可能性,不用再考虑其他组合。
-
**为什么要更新 min_val 和 max_val?**遍历到下一个数组时,"前面所有数组" 的范围变大了,需要把当前数组的极值加入,让后续计算更全面(比如遍历到第三个数组时,"前面所有数组" 包含前两个,所以要更新全局极值)。
总结(一句话记住核心)
遍历每个数组时,只和 "前面所有数组" 的最小 / 最大值算距离,同时把当前数组的极值加入全局,最终的最大距离就是答案。
这个写法的优点:
- 只用 3 个核心变量(res/min_val/max_val),没有复杂的索引、次小次大;
- 遍历一次就搞定,时间复杂度 O (m)(m 是数组个数),效率最高;
- 天然避开 "同数组选数" 的坑,不用处理复杂的边界条件。