给定一个无序的数组 nums
,返回 数组在排序之后,相邻元素之间最大的差值 。如果数组元素个数小于 2,则返回 0
。
您必须编写一个在「线性时间」内运行并使用「线性额外空间」的算法。
示例 1:
**输入:** nums = [3,6,9,1]
**输出:** 3
**解释:** 排序后的数组是 [1,3,6,9] ** _,_** 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。
示例 2:
**输入:** nums = [10]
**输出:** 0
**解释:** 数组元素个数小于 2,因此返回 0。
提示:
1 <= nums.length <= 105
0 <= nums[i] <= 109
方法一:基数排序 思路与算法
一种最简单的思路是将数组排序后再找出最大间距,但传统的基于比较的排序算法(快速排序、归并排序等)均需要 $O(N\log N)$ 的时间复杂度。如果要将时间复杂度降到 $O(N)$,我们就必须使用其他的排序算法。例如,基数排序 可以在 $O(N)$ 的时间内完成整数之间的排序。
代码
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution {public : int maximumGap (vector<int >& nums) { int n = nums.size (); if (n < 2 ) { return 0 ; } int exp = 1 ; vector<int > buf (n) ; int maxVal = *max_element (nums.begin (), nums.end ()); while (maxVal >= exp) { vector<int > cnt (10 ) ; for (int i = 0 ; i < n; i++) { int digit = (nums[i] / exp) % 10 ; cnt[digit]++; } for (int i = 1 ; i < 10 ; i++) { cnt[i] += cnt[i - 1 ]; } for (int i = n - 1 ; i >= 0 ; i--) { int digit = (nums[i] / exp) % 10 ; buf[cnt[digit] - 1 ] = nums[i]; cnt[digit]--; } copy (buf.begin (), buf.end (), nums.begin ()); exp *= 10 ; } int ret = 0 ; for (int i = 1 ; i < n; i++) { ret = max (ret, nums[i] - nums[i - 1 ]); } return ret; } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public int maximumGap (int [] nums) { int n = nums.length; if (n < 2 ) { return 0 ; } long exp = 1 ; int [] buf = new int [n]; int maxVal = Arrays.stream(nums).max().getAsInt(); while (maxVal >= exp) { int [] cnt = new int [10 ]; for (int i = 0 ; i < n; i++) { int digit = (nums[i] / (int ) exp) % 10 ; cnt[digit]++; } for (int i = 1 ; i < 10 ; i++) { cnt[i] += cnt[i - 1 ]; } for (int i = n - 1 ; i >= 0 ; i--) { int digit = (nums[i] / (int ) exp) % 10 ; buf[cnt[digit] - 1 ] = nums[i]; cnt[digit]--; } System.arraycopy(buf, 0 , nums, 0 , n); exp *= 10 ; } int ret = 0 ; for (int i = 1 ; i < n; i++) { ret = Math.max(ret, nums[i] - nums[i - 1 ]); } return ret; } }
[sol1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 var maximumGap = function (nums ) { const n = nums.length ; if (n < 2 ) { return 0 ; } let exp = 1 ; const buf = new Array (n).fill (0 ); const maxVal = Math .max (...nums); while (maxVal >= exp) { const cnt = new Array (10 ).fill (0 ); for (let i = 0 ; i < n; i++) { let digit = Math .floor (nums[i] / exp) % 10 ; cnt[digit]++; } for (let i = 1 ; i < 10 ; i++) { cnt[i] += cnt[i - 1 ]; } for (let i = n - 1 ; i >= 0 ; i--) { let digit = Math .floor (nums[i] / exp) % 10 ; buf[cnt[digit] - 1 ] = nums[i]; cnt[digit]--; } nums.splice (0 , n, ...buf); exp *= 10 ; } let ret = 0 ; for (let i = 1 ; i < n; i++) { ret = Math .max (ret, nums[i] - nums[i - 1 ]); } return ret; };
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 func maximumGap (nums []int ) (ans int ) { n := len (nums) if n < 2 { return } buf := make ([]int , n) maxVal := max(nums...) for exp := 1 ; exp <= maxVal; exp *= 10 { cnt := [10 ]int {} for _, v := range nums { digit := v / exp % 10 cnt[digit]++ } for i := 1 ; i < 10 ; i++ { cnt[i] += cnt[i-1 ] } for i := n - 1 ; i >= 0 ; i-- { digit := nums[i] / exp % 10 buf[cnt[digit]-1 ] = nums[i] cnt[digit]-- } copy (nums, buf) } for i := 1 ; i < n; i++ { ans = max(ans, nums[i]-nums[i-1 ]) } return } func max (a ...int ) int { res := a[0 ] for _, v := range a[1 :] { if v > res { res = v } } return res }
[sol1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 int maximumGap (int * nums, int numsSize) { if (numsSize < 2 ) { return 0 ; } int exp = 1 ; int buf[numsSize]; memset (buf, 0 , sizeof (buf)); int maxVal = INT_MIN; for (int i = 0 ; i < numsSize; ++i) { maxVal = fmax(maxVal, nums[i]); } while (maxVal >= exp ) { int cnt[10 ]; memset (cnt, 0 , sizeof (cnt)); for (int i = 0 ; i < numsSize; i++) { int digit = (nums[i] / exp ) % 10 ; cnt[digit]++; } for (int i = 1 ; i < 10 ; i++) { cnt[i] += cnt[i - 1 ]; } for (int i = numsSize - 1 ; i >= 0 ; i--) { int digit = (nums[i] / exp ) % 10 ; buf[cnt[digit] - 1 ] = nums[i]; cnt[digit]--; } memcpy (nums, buf, sizeof (int ) * numsSize); exp *= 10 ; } int ret = 0 ; for (int i = 1 ; i < numsSize; i++) { ret = fmax(ret, nums[i] - nums[i - 1 ]); } return ret; }
复杂度分析
方法二:基于桶的算法 思路与算法
设长度为 $N$ 的数组中最大值为 $\textit{max,min}$,则不难发现相邻数字的最大间距不会小于 $\lceil (\textit{max}-\textit{min}) / (N-1) \rceil$。
为了说明这一点,我们使用反证法:假设相邻数字的间距都小于 $\lceil (\textit{max}-\textit{min}) / (N-1) \rceil$,并记数组排序后从小到大的数字依次为 $A_1, A_2, …, A_N$,则有
$$ \begin{aligned} A_N - A_1&=(A_N - A_{N-1})+(A_{N-1}-A_{N-2})+ … + (A_2 - A_1) \ &< \lceil (\textit{max}-\textit{min}) / (N-1) \rceil + \lceil (\textit{max}-\textit{min}) / (N-1) \rceil + … + \lceil (\textit{max}-\textit{min}) / (N-1) \rceil \ &< (N-1) \cdot \lceil (\textit{max}-\textit{min}) / (N-1) \rceil= \textit{max}-\textit{min} \end{aligned} $$
但根据 $A_1, A_N$ 的定义,一定有 $A_1=\textit{min}$,且 $A_N=\textit{max}$,故上式会导出矛盾。
因此,我们可以选取整数 $d = \lfloor (\textit{max}-\textit{min}) / (N-1) \rfloor < \lceil (\textit{max}-\textit{min}) / (N-1) \rceil$。随后,我们将整个区间划分为若干个大小为 $d$ 的桶,并找出每个整数所在的桶。根据前面的结论,能够知道,元素之间的最大间距一定不会出现在某个桶的内部,而一定会出现在不同桶当中。
因此,在找出每个元素所在的桶之后,我们可以维护每个桶内元素的最大值与最小值。随后,只需从前到后不断比较相邻的桶,用后一个桶的最小值与前一个桶的最大值之差作为两个桶的间距,最终就能得到所求的答案。
代码
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution {public : int maximumGap (vector<int >& nums) { int n = nums.size (); if (n < 2 ) { return 0 ; } int minVal = *min_element (nums.begin (), nums.end ()); int maxVal = *max_element (nums.begin (), nums.end ()); int d = max (1 , (maxVal - minVal) / (n - 1 )); int bucketSize = (maxVal - minVal) / d + 1 ; vector<pair<int , int >> bucket (bucketSize, {-1 , -1 }); for (int i = 0 ; i < n; i++) { int idx = (nums[i] - minVal) / d; if (bucket[idx].first == -1 ) { bucket[idx].first = bucket[idx].second = nums[i]; } else { bucket[idx].first = min (bucket[idx].first, nums[i]); bucket[idx].second = max (bucket[idx].second, nums[i]); } } int ret = 0 ; int prev = -1 ; for (int i = 0 ; i < bucketSize; i++) { if (bucket[i].first == -1 ) continue ; if (prev != -1 ) { ret = max (ret, bucket[i].first - bucket[prev].second); } prev = i; } return ret; } };
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution { public int maximumGap (int [] nums) { int n = nums.length; if (n < 2 ) { return 0 ; } int minVal = Arrays.stream(nums).min().getAsInt(); int maxVal = Arrays.stream(nums).max().getAsInt(); int d = Math.max(1 , (maxVal - minVal) / (n - 1 )); int bucketSize = (maxVal - minVal) / d + 1 ; int [][] bucket = new int [bucketSize][2 ]; for (int i = 0 ; i < bucketSize; ++i) { Arrays.fill(bucket[i], -1 ); } for (int i = 0 ; i < n; i++) { int idx = (nums[i] - minVal) / d; if (bucket[idx][0 ] == -1 ) { bucket[idx][0 ] = bucket[idx][1 ] = nums[i]; } else { bucket[idx][0 ] = Math.min(bucket[idx][0 ], nums[i]); bucket[idx][1 ] = Math.max(bucket[idx][1 ], nums[i]); } } int ret = 0 ; int prev = -1 ; for (int i = 0 ; i < bucketSize; i++) { if (bucket[i][0 ] == -1 ) { continue ; } if (prev != -1 ) { ret = Math.max(ret, bucket[i][0 ] - bucket[prev][1 ]); } prev = i; } return ret; } }
[sol2-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 var maximumGap = function (nums ) { const n = nums.length ; if (n < 2 ) { return 0 ; } const minVal = Math .min (...nums); const maxVal = Math .max (...nums); const d = Math .max (1 , Math .floor (maxVal - minVal) / (n - 1 )); const bucketSize = Math .floor ((maxVal - minVal) / d) + 1 ; const bucket = new Array (bucketSize).fill (0 ).map (x => new Array (2 ).fill (0 )); for (let i = 0 ; i < bucketSize; ++i) { bucket[i].fill (-1 ); } for (let i = 0 ; i < n; i++) { const idx = Math .floor ((nums[i] - minVal) / d); if (bucket[idx][0 ] === -1 ) { bucket[idx][0 ] = bucket[idx][1 ] = nums[i]; } else { bucket[idx][0 ] = Math .min (bucket[idx][0 ], nums[i]); bucket[idx][1 ] = Math .max (bucket[idx][1 ], nums[i]); } } let ret = 0 ; let prev = -1 ; for (let i = 0 ; i < bucketSize; i++) { if (bucket[i][0 ] == -1 ) { continue ; } if (prev != -1 ) { ret = Math .max (ret, bucket[i][0 ] - bucket[prev][1 ]); } prev = i; } return ret; };
[sol2-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 type pair struct { min, max int }func maximumGap (nums []int ) (ans int ) { n := len (nums) if n < 2 { return } minVal := min(nums...) maxVal := max(nums...) d := max(1 , (maxVal-minVal)/(n-1 )) bucketSize := (maxVal-minVal)/d + 1 buckets := make ([]pair, bucketSize) for i := range buckets { buckets[i] = pair{-1 , -1 } } for _, v := range nums { bid := (v - minVal) / d if buckets[bid].min == -1 { buckets[bid].min = v buckets[bid].max = v } else { buckets[bid].min = min(buckets[bid].min, v) buckets[bid].max = max(buckets[bid].max, v) } } prev := -1 for i, b := range buckets { if b.min == -1 { continue } if prev != -1 { ans = max(ans, b.min-buckets[prev].max) } prev = i } return } func min (a ...int ) int { res := a[0 ] for _, v := range a[1 :] { if v < res { res = v } } return res } func max (a ...int ) int { res := a[0 ] for _, v := range a[1 :] { if v > res { res = v } } return res }
[sol2-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 int maximumGap (int * nums, int numsSize) { if (numsSize < 2 ) { return 0 ; } int maxVal = INT_MIN, minVal = INT_MAX; for (int i = 0 ; i < numsSize; ++i) { maxVal = fmax(maxVal, nums[i]); minVal = fmin(minVal, nums[i]); } int d = fmax(1 , (maxVal - minVal) / (numsSize - 1 )); int bucketSize = (maxVal - minVal) / d + 1 ; int bucket[bucketSize][2 ]; memset (bucket, -1 , sizeof (bucket)); for (int i = 0 ; i < numsSize; i++) { int idx = (nums[i] - minVal) / d; if (bucket[idx][0 ] == -1 ) { bucket[idx][0 ] = bucket[idx][1 ] = nums[i]; } else { bucket[idx][0 ] = fmin(bucket[idx][0 ], nums[i]); bucket[idx][1 ] = fmax(bucket[idx][1 ], nums[i]); } } int ret = 0 ; int prev = -1 ; for (int i = 0 ; i < bucketSize; i++) { if (bucket[i][0 ] == -1 ) continue ; if (prev != -1 ) { ret = fmax(ret, bucket[i][0 ] - bucket[prev][1 ]); } prev = i; } return ret; }
复杂度分析