解法步骤说明
- 先对两个数组排序
- 尽量减少要遍历的区间
- 双指针遍历计算结果
可以把两个数组看成是整数数轴上的两个区间(各自是由数组最大值和最小值限定的离散的区间,但是可以把它们想象成连续的区间或者说线段)。通过分析这两个区间的位置关系可以理清对“减小要遍历的区间”的处理过程。
- 存在区间边界相等的情况
- 两区间没有交集
- 两区间中的一个完全包含了另一个
- 两区间有部分交集
这道题我花了大力气在 “尽量减小要遍历的区间” 这一点上。需要强调的是,排序的时间复杂度是,后续遍历计算结果的时间复杂度是,时间主要花在排序上,所以“尽量减小要遍历的区间”这一点优化对整体性能提升的作用不大。我只是觉得有趣,写写看而已。假设有这样的题目,输入的两个数组(区间)已经有序,要求合并数组(区间),或者合并是计算过程的一部分,此时减小要遍历的数组(区间)的长度是有意义的。
读者可以自行修改,将减小遍历区间的部分去掉以简化代码。具体细节请看下面的代码和注释。
import "math"
import "sort"
func smallestDifference(a []int, b []int) int {
la := len(a)
if la == 0 {
panic("a is empty")
}
lb := len(b)
if lb == 0 {
panic("b is empty")
}
// la != 0 && lb != 0
sort.Sort(sort.IntSlice(a))
sort.Sort(sort.IntSlice(b))
// 先看看能不能快速得到结果
// 端点相等,4种情况
if a[0] == b[0] ||
a[0] == b[lb-1] ||
a[la-1] == b[0] ||
a[la-1] == b[lb-1] {
return 0
}
// 不相交,两种情况
if a[la-1] < b[0] {
return b[0] - a[la-1]
}
if b[lb-1] < a[0] {
return a[0] - b[lb-1]
}
// 下面先尝试缩小遍历区间,找比最小值小的最大值,找比最大值大的最小值。
// 先判断完全包含,两种情况
// a[0] < b[0] && b[lb-1] < a[la-1]
// a包含b
minDiff, ok := handleInclusion(a, b)
if ok {
return minDiff
}
// b[0] < a[0] && a[la-1] < b[lb-1]
// b包含a
minDiff, ok = handleInclusion(b, a)
if ok {
return minDiff
}
// 再判断相交(不完全包含),两种情况
// a[la-1] > b[0] && a[la-1] < b[lb-1]
// a的右端在b中
minDiff, ok = handleIntersection(a, b)
if ok {
return minDiff
}
// b的右端在a中
minDiff, ok = handleIntersection(b, a)
if ok {
return minDiff
}
// 兜底
return getMinAbsDiff(a, b)
}
// 尝试处理相交的情况
func handleIntersection(a, b []int) (int, bool) {
var (
la = len(a)
lb = len(b)
)
// 相交(不完全包含)
if a[la-1] > b[0] && a[la-1] < b[lb-1] { // a的右端在b中
// 尝试缩小b的要遍历的区间。
// 在b中找比a[la-1]大的最小值。
bRight, equal := searchMinValueGreaterThanX(b, a[la-1])
if equal {
return 0, true
}
// 尽量缩小a的要遍历的区间。
// 在a中找比b[0]小的最大值。
aLeft, equal := searchMaxValueSmallerThanX(a, b[0])
if equal {
return 0, true
}
return getMinAbsDiff(a[aLeft:], b[:bRight+1]), true
}
return 0, false
}
// 尝试处理包含的情况
func handleInclusion(a, b []int) (int, bool) {
var (
la = len(a)
lb = len(b)
)
// 完全包含
if a[0] < b[0] && b[lb-1] < a[la-1] { // a包含b
// 缩小a的要遍历的区间
// 在a中找比b[0]小的最大值
aLeft, equal := searchMaxValueSmallerThanX(a, b[0])
if equal {
return 0, true
}
// 在a中找比b[lb-1]大的最小值
aRight, equal := searchMinValueGreaterThanX(a, b[lb-1])
if equal {
return 0, true
}
return getMinAbsDiff(a[aLeft:aRight+1], b), true
}
return 0, false
}
// 双指针遍历,计算结果
func getMinAbsDiff(a, b []int) int {
var (
ia int
ib int
la = len(a)
lb = len(b)
minDiff = math.MaxInt32
)
for ia < la && ib < lb {
if a[ia] == b[ib] {
return 0
}
if a[ia] < b[ib] {
x := b[ib] - a[ia]
if x < minDiff {
minDiff = x
}
ia++
} else {
x := a[ia] - b[ib]
if x < minDiff {
minDiff = x
}
ib++
}
}
return minDiff
}
// 在有序数组a中找比x小的最大值。
// 如果找到,返回那个元素的索引。
// 如果都比x大,返回0。
// 如果返回值中的bool是true,说明找到了等于x的值。
func searchMaxValueSmallerThanX(a []int, x int) (int, bool) {
var (
low int
high = len(a)-1
maxVal int
i = -1
)
for low <= high {
mid := low + (high-low)>>1
v := a[mid]
if v == x {
return mid, true
}
if v < x {
if i != -1 {
if v > maxVal {
maxVal = v
i = mid
}
} else {
maxVal = v
i = mid
}
low = mid+1
} else {
high = mid-1
}
}
// 都比x大
if high == -1 {
return low, false
}
return i, false
}
// 在有序数组a中找比x大的最小值。
// 如果找到,返回那个元素的索引。
// 如果都比x小,返回a最大索引。
// 如果返回值中的bool是true,说明找到了等于x的值。
func searchMinValueGreaterThanX(a []int, x int) (int, bool) {
var (
low int
high = len(a)-1
minVal int
i = -1
)
for low <= high {
mid := low + (high-low)>>1
v := a[mid]
if v == x {
return mid, true
}
if v < x {
low = mid+1
} else {
if i != -1 {
if v < minVal {
minVal = v
i = mid
}
} else {
minVal = v
i = mid
}
high = mid-1
}
}
// 都比x小
if low == len(a) {
return high, false
}
return i, false
}
排序(假设基于快速排序)时间复杂度,空间复杂度(系统栈开销)。
尝试减小遍历区间的操作基于二分查找,时间复杂度是。
遍历计算结果的时间复杂度。
整体上,时间复杂度,空间复杂度。