Leetcode 72 数组列表中的最大距离

1 题目

624. 数组列表中的最大距离

给定 m 个数组,每个数组都已经按照升序排好序了。

现在你需要从两个不同的数组中选择两个整数(每个数组选一个)并且计算它们的距离。两个整数 ab 之间的距离定义为它们差的绝对值 |a-b|

返回最大距离。

示例 1:

复制代码
输入:[[1,2,3],[4,5],[1,2,3]]
输出:4
解释:
一种得到答案 4 的方法是从第一个数组或者第三个数组中选择 1,同时从第二个数组中选择 5 。

示例 2:

复制代码
输入:arrays = [[1],[1]]
输出:0

提示:

  • m == arrays.length
  • 2 <= m <= 105
  • 1 <= arrays[i].length <= 500
  • -104 <= arrays[i][j] <= 104
  • arrays[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)

这个版本更贴近你的初始思路:

  1. 先按你想的 "遍历更 minNum/maxNum",同时记索引;
  2. 发现同数组后,再遍历一次找 "排除该数组后的次小 / 次大";
  3. 最后比较两种情况。

虽然比之前的版本多了一次遍历,但逻辑更直白,完全对应你 "先找最小 / 最大,再处理异常" 的思考路径。

总结你纠结的点

你的疑问 核心答案
为什么只看 arr [0]/arr [-1]? 数组升序,首尾就是每个数组的最小 / 最大,不用看中间元素
怎么防止越界? 题目保证数组长度≥1,arr [0] 和 arr [-1] 一定存在,不会越界
只记 min/max 为什么不行? 可能来自同一个数组,违反 "不同数组选数" 的规则
怎么处理同数组的情况? 找 "排除该数组后的次小 / 次大",再计算差值

其实你一开始的思路已经对了 80%,只差最后一步 "处理同数组的异常情况"。这类题的关键就是:先抓核心规律(升序数组首尾是极值),再补全边界条件(不同数组)

你可以把这个思路套到之前做过的题里对比:

    1. 买卖股票:核心是 "低买高卖",边界是 "买在卖之前";
    1. 最大增量:核心是 "后数 - 前数最大",边界是 "后数 > 前数";
  • 本题:核心是 "最大 - 最小",边界是 "最大最小来自不同数组"。

本质都是 "先找核心解,再修边界",掌握这个逻辑,这类题就通了。


题解

先明确核心前提(题目给的关键条件)

题目里说:每个数组都是升序排列的 → 这意味着:

  • 任意一个数组的最小值一定是第一个元素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 → 现在resmax(0,4)=4

  • ✨ 含义:第二个数组的最大值(5) - 第一个数组的最小值(1),这是一个候选的最大距离;

  • 行 8:计算abs(3 - 4) = 1 → 现在resmax(4,1)=4

  • ✨ 含义:第一个数组的最大值(3) - 第二个数组的最小值(4),这个距离更小,不更新;

  • 行 9:更新min_val = min(1,4) = 1(当前全局最小值还是 1);

  • 行 10:更新max_val = max(3,5) = 5(当前全局最大值变成 5);

  • → 本轮结束后:res=4min_val=1max_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) = 2resmax(4,2)=4

  • ✨ 含义:第三个数组的最大值(3) - 前两个数组的最小值(1),距离 2,比当前 res 小;

  • 行 8:计算abs(5 - 1) = 4resmax(4,4)=4

  • ✨ 含义:前两个数组的最大值(5) - 第三个数组的最小值(1),距离 4,和当前 res 相等;

  • 行 9:更新min_val = min(1,1) = 1(全局最小值还是 1);

  • 行 10:更新max_val = max(5,3) = 5(全局最大值还是 5);

  • → 本轮结束后:res=4min_val=1max_val=5

第四步:返回结果(行 11)

循环结束,返回res=4,和示例的正确输出一致!

再用 "极值同数组" 的例子验证(输入[[1,100],[2,3],[4,5]]

初始化:
  • res=0min_val=1(第一个数组最小值),max_val=100(第一个数组最大值);
第一次循环(i=1,数组[2,3]):
  • curr_min=2curr_max=3
  • 行 7:abs(3-1)=2res=2
  • 行 8:abs(100-2)=98res=98
  • 行 9:min_val=min(1,2)=1
  • 行 10:max_val=max(100,3)=100
第二次循环(i=2,数组[4,5]):
  • curr_min=4curr_max=5
  • 行 7:abs(5-1)=4res=max(98,4)=98
  • 行 8:abs(100-4)=96res=max(98,96)=98
  • 行 9:min_val=1
  • 行 10:max_val=100
返回结果:98(正确,因为最大距离是 100-2=98)。

核心逻辑的 "底层原理"(为什么这样写能解决问题)

  1. 为什么从 i=1 开始遍历? 第一个数组作为 "基准",后续每个数组只和 "前面所有数组" 比较,天然避免了 "同一个数组选数" 的坑(比如不会出现 "100-1" 这种同数组的情况)。

  2. **为什么要算行 7 和行 8 两种情况?**最大距离只有两种可能:

    • 情况 1:当前数组的最大值,减去 "前面所有数组" 的最小值(比如 5-1=4);
    • 情况 2:"前面所有数组" 的最大值,减去当前数组的最小值(比如 100-2=98);这两种情况覆盖了 "跨数组找最大距离" 的所有可能性,不用再考虑其他组合。
  3. **为什么要更新 min_val 和 max_val?**遍历到下一个数组时,"前面所有数组" 的范围变大了,需要把当前数组的极值加入,让后续计算更全面(比如遍历到第三个数组时,"前面所有数组" 包含前两个,所以要更新全局极值)。

总结(一句话记住核心)

遍历每个数组时,只和 "前面所有数组" 的最小 / 最大值算距离,同时把当前数组的极值加入全局,最终的最大距离就是答案

这个写法的优点:

  • 只用 3 个核心变量(res/min_val/max_val),没有复杂的索引、次小次大;
  • 遍历一次就搞定,时间复杂度 O (m)(m 是数组个数),效率最高;
  • 天然避开 "同数组选数" 的坑,不用处理复杂的边界条件。
相关推荐
FFF团团员9092 小时前
树莓派学习笔记7:局域网的建立和程序自启动
笔记·学习
编程饭碗2 小时前
【Java循环】
java·服务器·算法
曾几何时`2 小时前
归并排序(一)
数据结构·算法·leetcode
码以致用2 小时前
Kafka笔记
笔记·分布式·kafka
Dream it possible!3 小时前
LeetCode 面试经典 150_图的广度优先搜索_最小基因变化(93_433_C++_中等)(广度优先搜索(BFS))
c++·leetcode·面试·广度优先
kkkkkkkkk_12013 小时前
【强化学习】05周博磊强化学习纲要学习笔记——第三课上
笔记·学习·强化学习
CoovallyAIHub3 小时前
何必先OCR再LLM?视觉语言模型直接读图,让百页长文档信息不丢失
深度学习·算法·计算机视觉
淳杰3 小时前
【Androidstudio】学习/采坑笔记-冷重启和热重启(reboot)
笔记·学习
CoovallyAIHub4 小时前
NAN-DETR:集中式噪声机制如何让检测更“团结”?
深度学习·算法·计算机视觉