# 1906. 查询差绝对值的最小值

### 题目地址(1906. 查询差绝对值的最小值)

<https://leetcode-cn.com/problems/minimum-absolute-difference-queries/>

### 题目描述

```
一个数组 a 的 差绝对值的最小值 定义为：0 <= i < j < a.length 且 a[i] != a[j] 的 |a[i] - a[j]| 的 最小值。如果 a 中所有元素都 相同 ，那么差绝对值的最小值为 -1 。

比方说，数组 [5,2,3,7,2] 差绝对值的最小值是 |2 - 3| = 1 。注意答案不为 0 ，因为 a[i] 和 a[j] 必须不相等。

给你一个整数数组 nums 和查询数组 queries ，其中 queries[i] = [li, ri] 。对于每个查询 i ，计算 子数组 nums[li...ri] 中 差绝对值的最小值 ，子数组 nums[li...ri] 包含 nums 数组（下标从 0 开始）中下标在 li 和 ri 之间的所有元素（包含 li 和 ri 在内）。

请你返回 ans 数组，其中 ans[i] 是第 i 个查询的答案。

子数组 是一个数组中连续的一段元素。

|x| 的值定义为：

如果 x >= 0 ，那么值为 x 。
如果 x < 0 ，那么值为 -x 。

 

示例 1：

输入：nums = [1,3,4,8], queries = [[0,1],[1,2],[2,3],[0,3]]
输出：[2,1,4,1]
解释：查询结果如下：
- queries[0] = [0,1]：子数组是 [1,3] ，差绝对值的最小值为 |1-3| = 2 。
- queries[1] = [1,2]：子数组是 [3,4] ，差绝对值的最小值为 |3-4| = 1 。
- queries[2] = [2,3]：子数组是 [4,8] ，差绝对值的最小值为 |4-8| = 4 。
- queries[3] = [0,3]：子数组是 [1,3,4,8] ，差的绝对值的最小值为 |3-4| = 1 。


示例 2：

输入：nums = [4,5,2,2,7,10], queries = [[2,3],[0,2],[0,5],[3,5]]
输出：[-1,1,1,3]
解释：查询结果如下：
- queries[0] = [2,3]：子数组是 [2,2] ，差绝对值的最小值为 -1 ，因为所有元素相等。
- queries[1] = [0,2]：子数组是 [4,5,2] ，差绝对值的最小值为 |4-5| = 1 。
- queries[2] = [0,5]：子数组是 [4,5,2,2,7,10] ，差绝对值的最小值为 |4-5| = 1 。
- queries[3] = [3,5]：子数组是 [2,7,10] ，差绝对值的最小值为 |7-10| = 3 。


 

提示：

2 <= nums.length <= 10^5
1 <= nums[i] <= 100
1 <= queries.length <= 2 * 10^4
0 <= li < ri < nums.length
```

### 前置知识

* 前缀和
* 离散化

### 公司

* 暂无

### 思路

由于需要查询任意区间 \[ql,qr] 的差的最小值。因此一种简单的思路是对 nums 进行一次排序，之后遍历 \[ql,qr] 的所有相邻差，并取最小的即可。这种做法时间复杂度为排序 $nlogn$ + 查询 $n^2$，代入题目数据范围 $2 <= nums.length <= 10^5$，则必然超时。

一种优化的思路是对 nums 的数据进行离散化，即将值映射到一个大小为 nums 值域大小的数组（由于 1 <= nums\[i] <= 100 ，因此对于这道题来说至于大小就是 100）。这样通过遍历值域数组就可以得到最小绝对值差。由于值域大小最多是 100，相比于 $10^5$ 是巨大优化。

不过值域数组不含索引信息，因此我想求区间 \[ql,qr] 的差的最小值则无法直接做到，我们只能求区间 \[0,n-1] 的差的最小值。

那怎么办呢？

我们建立 n 个值域数组，即对每一个索引 i 都建立一个值域数组。这样区间 \[ql,qr]的值就可以通过前缀和求得。比如 ：

```py
for i in range(1, 101):
    v = pres[qr+1][i] - pres[ql][i]
```

v 就是 i 在 \[ql,qr] 的出现次数。

也就是说**我们可以建议二维数组 pre\[i]\[j] 表示数组 nums 前 i 项 j 出现的次数**，这样可以通过前缀和求出任意区间任意数出现次数。

剩下的就容易了，具体参考下方代码。

这种做法本质上空间换时间，即使用值域的空间大小换取嵌套 n 循环的实际，是一种典型的取舍。也就是说如果题目值域很大，比如 $10^7$ ，那就不适合使用此算法了。

### 关键点

* 同时对索引和值建立前缀和，即建立二维前缀和

### 代码

* 语言支持：Python3

Python3 Code:

```python

class Solution:
    def minDifference(self, nums: List[int], queries: List[List[int]]) -> List[int]:
        ans = []
        n = len(nums)
        pres = [[0] * 101]
        for i, num in enumerate(nums):
            pres.append(pres[-1].copy())
            pres[-1][num] += 1

        for ql, qr in queries:
            pre = -100
            cur = 100
            for i in range(1, 101):
                if pres[qr+1][i] - pres[ql][i] > 0:
                    cur = min(cur, i - pre)
                    pre = i
            if cur >= 100: ans.append(-1)
            else: ans.append(cur)
        return ans

```

**复杂度分析**

令 n 为数组长度，q 为 queries 长度，v 为 nums 取值范围（这里是 100）。

* 时间复杂度：$O(nvq)$
* 空间复杂度：$O(nv)$

> 此题解由 [力扣刷题插件](https://leetcode-pp.github.io/leetcode-cheat/?tab=solution-template) 自动生成。

力扣的小伙伴可以[关注我](https://leetcode-cn.com/u/fe-lucifer/)，这样就会第一时间收到我的动态啦\~

以上就是本文的全部内容了。大家对此有何看法，欢迎给我留言，我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库：<https://github.com/azl397985856/leetcode> 。 目前已经 40K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。

关注公众号力扣加加，努力用清晰直白的语言还原解题思路，并且有大量图解，手把手教你识别套路，高效刷题。

![](https://p.ipic.vip/59w59k.jpg)
