最大間距
1.題目
給定一個無序的數組,找出數組在排序之後,相鄰元素之間最大的差值。
如果數組元素個數小於 2,則返回 0。
示例 1
輸入: [3,6,9,1]
輸出: 3
解釋: 排序後的數組是 [1,3,6,9], 其中相鄰元素 (3,6) 和 (6,9) 之間都存在最大差值 3。
示例 2
輸入: [10]
輸出: 0
解釋: 數組元素個數小於 2,因此返回 0。
說明:
- 你可以假設數組中所有元素都是非負整數,且數值在 32 位有符號整數範圍內。
- 請嘗試在線性時間複雜度和空間複雜度的條件下解決此問題。
題目模板
/**
* @param {number[]} nums
* @return {number}
*/
var maximumGap = function(nums) {
};
2.思路分析
傳統方法:先排序,再遍歷一遍,遍歷過程中找到最大差值
升級後:在排序過程中找到最大差值
3.所用到的方法
見題解
4.題解及優化
傳統方法:
let maximumGap = (nums) => {
let maxGap = 0
if (nums.length < 2) return maxGap
// 排序
for (let i = nums.length - 1; i > 0; i--) {
for (let j = 0; j < i; j++) {
[nums[j], nums[j + 1]] = nums[j] > nums[j + 1] ? [nums[j + 1], nums[j]] : [nums[j], nums[j + 1]]
}
}
nums.reduce((pre, cur) => {
maxGap = cur - pre > maxGap ? cur - pre : maxGap
return cur
})
return maxGap
}
在排序過程中緩存最大間距:
let maxGap = 0
if (nums.length < 2) return maxGap
if (nums.length === 2) return nums[1] - nums[0] > maxGap ? nums[1] - nums[0] : maxGap
// 排序
for (let i = nums.length - 1; i > 0; i--) {
for (let j = 0; j < i; j++) {
[nums[j], nums[j + 1]] = nums[j] > nums[j + 1] ? [nums[j + 1], nums[j]] : [nums[j], nums[j + 1]]
}
if (i < nums.length - 1) maxGap = nums[i + 1] - nums[i] > maxGap ? nums[i + 1] - nums[i] : maxGap
}
return maxGap
結果並不如意
使用基數排序
let maximumGap = (nums) => {
let maxGap = 0
if (nums.length < 2) return maxGap
let mod = 10 // 進制/基數,常見還有以radix爲變量名
let dev = 1 // 用來取進制位數據
let maxDigit = Math.max(...nums).toString().length // 最大位數
let counter = []
for (let i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
for (let j = 0; j < nums.length; j++) {
// 提取相應進制位的數據(個位/十位/百位...),相當於桶排序的桶的編號
let bucket = Math.floor((nums[j] % mod) / dev)
// 初始化統計數組
if (counter[bucket] == null) {
counter[bucket] = []
}
// 對應入組(這步相當於排序),利用LSD(次位優先)進行基數排序
counter[bucket].push(nums[j])
}
let pos = 0
for (let j = 0; j < counter.length; j++) {
let value = null
if (counter[j] != null) {
while ((value = counter[j].shift()) != null) {
nums[pos++] = value
}
}
}
// console.log('nums', nums) // 這裏可以得到按個位、十位...排序結果
}
nums.reduce((pre, cur) => {
maxGap = cur - pre > maxGap ? cur - pre : maxGap
return cur
})
return maxGap
}
還不錯
結合桶排序和鴿籠原理:
let maximumGap = (nums) => {
if (nums.length < 2) return 0
let min = Math.min(...nums) // 最小值
let max = Math.max(...nums) // 最大值
if (max - min === 0) return 0
let gap = Math.ceil((max - min) / (nums.length - 1)) // 均差(兩兩差值的平均值向下取整)
// 桶(區間)下限、桶(區間)上限(填充的目的是跳過空桶(區間))
let bucketsMin = new Array(nums.length - 1).fill(max) // Number.MAX_SAFE_INTEGER
let bucketsMax = new Array(nums.length - 1).fill(min)
for (let i = 0; i < nums.length; i++) {
if (nums[i] === min || nums[i] === max) continue // 桶中不容納最大最小兩個值
let idx = Math.floor((nums[i] - min) / gap) // 定位正確的桶
// 核心步驟:統計過程中只留下桶(區間)上下限,去掉其他無關中間元素(最大間距一定是大於均差的,一定產生在邊緣值之差之間)
bucketsMin[idx] = Math.min(nums[i], bucketsMin[idx]) // 統計桶(區間)下限
bucketsMax[idx] = Math.max(nums[i], bucketsMax[idx]) // 統計桶(區間)上限
}
let maxGap = 0 // 存儲最大間隔
let pre = min
for (let i = 0; i < nums.length - 1; i++) {
if (bucketsMax[i] === min) continue // 跳過空桶(區間)
// 核心步驟:統計邊緣值之差的最大值(還有一步在循環外)
maxGap = Math.max(maxGap, bucketsMin[i] - pre) // 本區間下限 與 上一個區間(空區間略過)上限 差值 的 最大值(第一個區間下限與最小值差值也在比較之中)
pre = bucketsMax[i]
}
maxGap = Math.max(maxGap, max - pre) // 最後非空一個區間下限與最大值差值也參與比較
return maxGap
}
鴿籠原理也稱抽屜原理:n 個物品放入 m 個容器中,如果 n > m 那麼一定有一個容器裝有至少兩個物品。同理n個桶,放m個物體,如果 n > m 那麼一定有一個容器是空桶。
假設對於數組中的任意一個元素都有一個桶,那麼每個元素恰好佔據一個桶。現在增加桶的個數,必然會有至少一個桶是空桶,那麼這個空桶附近的兩個元素之差就是我們想要的結果
代碼中將兩個最值放到最兩邊的桶內,剩下 數組元素個數-1 個桶,在元素入桶過程中不免會有,多個元素入一個桶的情況發生,因此桶內只放屬於這個桶的最大值和最小值(區間上下限),然後尋找空桶的過程就是:
- 本區間下限 與 上一個區間(空區間略過)上限 差值 的 最大值
- 第一個區間下限與最小值差值也在比較之中
- 最後非空一個區間下限與最大值差值也參與比較
可以這樣理解:最大間隔一定是大於平均間隔的(),而平均間隔就是桶的容量。
圖片來自:LeetCode-164.MaximumGap(最大間距)
———————————