首页 滑动窗口最大值
文章
取消

滑动窗口最大值

滑动窗口最大值问题是这样的一个问题:

给定一个整数数组和一个正整数 k,有一个长度为 k 的窗口在数组上从左到右滑动,输出每次滑动时窗口的最大值。 如数组 [4,1,-3,6,7,10,5,14],k=3,输出数组 [4,6,7,10,10,14]。 解释如下: [[4,1,-3],6,7,10,5,14] -> 4 [4,[1,-3,6],7,10,5,14] -> 6 [4,1,[-3,6,7],10,5,14] -> 7 [4,1,-3,[6,7,10],5,14] -> 10 [4,1,-3,6,[7,10,5],14] -> 10 [4,1,-3,6,7,[10,5,14]] -> 14

这种问题的困难点在于每次窗口滑动时,窗口内要新增一个元素和删除一个元素,新增元素时可以使用上次最大值和新增的值作比较得到这次最大值,但删除元素时,如果删除的元素是最大值那么就不得不遍历窗口内所有的元素。所以按照常规思路解决的话,时间复杂度会很高。

单调队列这种数据结构就很适合解决这种经典问题。单调队列是一种元素单调递增或递减的队列,每次入队时,为了保持队列的单调性可能需要删除前面的元素,如队列 [1,2,7,10,11] 入队 9 时,需要删除 10 和 11,最终结果就是 [1,2,7,9]。

那么对于前面的滑动窗口最大值问题,可以用单调队列这样解决。

我们先定义一个单调队列:

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
type MonotonicQueue struct {
	data []int
	size int
}

func NewMonotonicQueue() *MonotonicQueue {
	return &MonotonicQueue{size: 0}
}

// 队尾添加元素,且让队列保持单调递减
func (q *MonotonicQueue) Push(v int) {
	for q.size > 0 && q.data[q.size-1] <= v {
		// 删除末尾元素 data[q.size-1]
		q.data = q.data[:q.size-1]
		q.size--
	}
	// 添加递减的数据
	q.data = append(q.data, v)
	q.size++
}

// 移除首元素
func (q *MonotonicQueue) Pop(v int) {
	if q.size > 0 && q.data[0] == v {
		// 删除 data[0]
		if q.size > 1 {
			q.data = q.data[1:]
		} else {
			q.data = []int{}
		}
		q.size--
	}
}

// 队列中最大值,即首元素
func (q *MonotonicQueue) Max() int {
	if q.size > 0 {
		return q.data[0]
	}
	return -1
}

再来解决滑动窗口问题:

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
func slideMax(data []int, k int) []int {
	var result []int
	if len(data) < k {
		// 数组长度比滑动窗口长度小
		return result
	}

	window := NewMonotonicQueue()
	for i, n := range data {
		if i < k-1 {
			// 先填充前 k-1 个
			window.Push(n)
		} else {
			// 往前滑动
			window.Push(n)
			// 记录窗口的最大值
			result = append(result, window.Max())
			window.Pop(data[i-k+1])
		}
	}
	return result
}

func main() {
	var s = []int{4, 1, -3, 6, 7, 10, 5, 14}
	r := slideMax(s, 3)
    // [4 6 7 10 10 14]
	fmt.Println(r)
}
本文由作者按照 CC BY 4.0 进行授权