1、看到题目首先会想到维护一个左右指针,分别指向滑动窗口的左右端,初始化时遍历计算出当前窗口中的最大值,每一次左右指针均同时向右移动一位,此时更新最大值。此时右指针新移入一个新的数值,左指针则可能存在如下两种情况:1)左指针移出的数值非上一个窗口的最大值;2)左指针移出的数值为上一个窗口的最大值。
面对这两种情况,如果新移入的值大于上一窗口的最大值,那么该值必为当前窗口的最大值,否则如果是情况1),当前窗口最大值仍为上一窗口最大值;若为情况2)则需要对当前窗口进行遍历重新确定最大值。按照上述思路实现代码如下(果然超出了时间限制,过不了):
func maxSlidingWindow(nums []int, k int) []int {
var res []int
var getMaxOfWin func(l, r int) int
getMaxOfWin=func(l, r int) int {
key:=l
for i:=l+1; i<=r; i++{
if nums[i]>nums[key]{ key=i }
}
return key
}
keyMax:=getMaxOfWin(0, k-1)
res=append(res, nums[keyMax])
for left, right:=1, k; right<len(nums); left, right=left+1, right+1{
if nums[right]>nums[keyMax]{
keyMax=right
res=append(res, nums[keyMax])
}else{
if keyMax>left{
res=append(res, nums[keyMax])
}else{
keyMax=getMaxOfWin(left, right)
res=append(res, nums[keyMax])
}
}
}
return res
}
2、但不难发现上述思路存在一个很明显的不足,那就是蓝色字体部分的操作,很容易引起重复的比较操作,这里就要开始学习新的知识了,算是较为复杂的算法堆(优先队列)它来了,所谓堆其实就是一颗二叉树,它排序但并没有完全排序(详细学习堆以及go中的相关标准库可以参考go标准库【数据结构系列】之堆heap - 简书 (jianshu.com)https://www.jianshu.com/p/80a3d7add522),它保证节点始终是大/小于左右子树节点的,这样比较容易知道当前的最大/最小值。结合堆之后,这道题目就将滑动的窗口以大根堆(树节点为堆中的最大值)的结构进行存储,每次窗口右移就将新的元素加入堆中。
注意:为了降低代码的复杂程度,在窗口左边界移动时并不需要每次左移都将对应位置的元素移除(这个操作相对复杂),当且仅当遇到情况2)时,即应该被移除的元素是堆顶,弹出堆顶元素(此时是直接弹出堆顶元素,相比于删除堆中元素操作难度降低),直至新的堆顶在窗口范围内。
这样只需要每一次记录堆顶元素的值,最后得到的即为所求最大值数组,实现代码如下。
type priorityQueue struct{
nums []int
queue []int
}
func (q priorityQueue) Len() int { return len(q.queue) }
func (q priorityQueue) Less(i, j int) bool {
return q.nums[q.queue[i]]>q.nums[q.queue[j]]
}
func (q priorityQueue) Swap(i, j int) {
q.queue[i], q.queue[j]=q.queue[j], q.queue[i]
}
func (q *priorityQueue) Push(x interface{}) {
q.queue=append(q.queue, x.(int))
}
func (q *priorityQueue) Pop() interface{} {
old:=q.queue
x:=old[len(old)-1]
q.queue=old[0:len(old)-1]
return x
}
func maxSlidingWindow(nums []int, k int) []int {
// 初始化优先队列
q:=&priorityQueue{ nums, make([]int, k) }
for i:=0; i<k; i++{ q.queue[i]=i }
heap.Init(q)
var res []int
for i:=k-1; i<len(nums); i++ { // 通过有边界进行遍历
// 移入右边界新元素
heap.Push(q, i)
for q.queue[0]<i-k+1{
heap.Pop(q)
}
res=append(res, nums[q.queue[0]])
}
return res
}
注意:由于需要使用下标确定当前的最大值是否已不在窗口内,所以选择用堆存储下标,但比较时仍然使用数组中对应的值进行比较。
补:对于Go语言的接口一直感觉不太能搞懂,heap库所对应的接口相关定义如下:
// heap接口
type Interface interface { // 该接口组合了另外的sort.Interface接口,所以也要实现里面的方法
sort.Interface
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.
}
// sort.Interface接口
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
官方题解给出的接口实现如下(在main函数中给 a 赋值为 nums)。
var a []int
type hp struct{ sort.IntSlice }
func (h hp) Less(i, j int) bool { return a[h.IntSlice[i]] > a[h.IntSlice[j]] }
func (h *hp) Push(v interface{}) { h.IntSlice = append(h.IntSlice, v.(int)) }
func (h *hp) Pop() interface{} { a := h.IntSlice; v := a[len(a)-1]; h.IntSlice = a[:len(a)-1]; return v }
我自己实现的接口如下。
type priorityQueue struct{
nums []int
queue []int
}
func (q priorityQueue) Len() int { return len(q.queue) }
func (q priorityQueue) Less(i, j int) bool { return q.nums[q.queue[i]]>q.nums[q.queue[j]] }
func (q priorityQueue) Swap(i, j int) { q.queue[i], q.queue[j]=q.queue[j], q.queue[i] }
func (q *priorityQueue) Push(x interface{}) { q.queue=append(q.queue, x.(int)) }
func (q *priorityQueue) Pop() interface{} {
old:=q.queue
x:=old[len(old)-1]
q.queue=old[0:len(old)-1]
return x
}
后面尝试在我自己实现的接口的基础上令queue为sort.IntSlice类型,尝试是否可以省略Len与Swap方法,实现的定义如下。
type priorityQueue struct{
nums []int
sort.IntSlice
}
func (pq priorityQueue) Less(i, j int) bool { return pq.nums[pq.IntSlice[i]]>pq.nums[pq.IntSlice[j]] }
func (pq *priorityQueue) Push(x interface{}) { pq.IntSlice=append(pq.IntSlice, x.(int)) }
func (pq *priorityQueue) Pop() interface{} {
old:=pq.IntSlice
x:=old[len(old)-1]
pq.IntSlice=old[0:len(old)-1]
return x
}
对比尝试过后发现Go中的接口真的很灵活,只要该对象实现了接口对应的所必须的方法,那么该对象就可以赋值为该接口,这里堆的接口里面包含了另一个接口,也就是因为这个把我搞晕了,还一会儿赋值给指针一会儿赋值给非指针。
最后发现完全没有必要想那么多,看一下接口对应的源码,只要按照源码实现各个部分即可,至于指针的部分,以heap为例,push与pop方法在heap接口中定义,需要使用指针
func (pq *priorityQueue) Push(x interface{})
func (pq *priorityQueue) Pop() interface{}
而Len,Less,Swap方法在sort.Interface中定义,而在我定义的结构中对应的结构用的本就是指针,所以不需要再针对指针操作(感觉说不清楚一样,表达困难了,只能看代码自己悟了🤦♂️)。