LeetCode 239 滑动窗口最大值(Go的接口)

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中定义,而在我定义的结构中对应的结构用的本就是指针,所以不需要再针对指针操作(感觉说不清楚一样,表达困难了,只能看代码自己悟了🤦‍♂️)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\],可以使用暴力解法来求解滑动窗口最大值。具体的做法是,遍历数组,对于每个窗口,使用一个内部循环来找到窗口中的最大值,并将其存储在结果数组中。时间复杂度为O(n*k),其中n为数组长度,k为窗口大小。 根据引用\[2\],还可以使用队列来求解滑动窗口最大值。具体的做法是,使用一个双端队列来维护一个单调递减的窗口。遍历数组,对于每个元素,首先判断队头是否在滑动窗口范围内,如果不在,则将其从队头移除。然后,将当前元素与队尾元素比较,如果当前元素大于队尾元素,则将队尾元素移除,直到队列为空或者当前元素小于等于队尾元素。最后,将当前元素的索引插入队尾。如果滑动窗口的元素个数达到了k个,并且始终维持在窗口中,就将队头元素加入答案数组中。时间复杂度为O(n),其中n为数组长度。 综上所述,可以使用暴力解法或者使用队列来求解leetcode滑动窗口最大值。 #### 引用[.reference_title] - *1* *3* [leetcode239. 滑动窗口最大值](https://blog.csdn.net/kkkkuuga/article/details/124829581)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Leetcode#239. 滑动窗口最大值 (Java解法)](https://blog.csdn.net/paranior/article/details/114890555)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值