@[TOC]([仓颉Cangjie刷题模板] 单调队列(含数组版双端队列实现) )
一、 算法&数据结构
1. 描述
单调队列通常用来维护窗口移动、扩展过程中,当前窗口最大/最小值的问题。
单调队列是一种贪心算法,可以将很多O(nlgn)的算法优化成O(n)
2. 复杂度分析
- 总体 O(n),每次查询均摊O(1)
3. 常见应用
- 滑动窗口的最大值
- 多重背包的单调队列优化
4. 常用优化
- 由于仓颉里未直接提供deque,而使用LinkList代替是链表模型,无法提供随机访问的能力(虽然也不需要),于是我手写了一版基于数组的实现。以Pythonic的方式提供了下标访问的功能。
二、 模板代码
1. 询问滑窗最大值
例题: 239. 滑动窗口最大值
单调队列
经典滑窗。
维护一个单调递降队列
,队首是答案
,队尾判断是否弹出。
窗口滑动时,判断队尾比i小的全部弹出,因为他们对后边的窗口来说贡献不会大于i。
滑动后判断队首元素是否窗外,弹出后,队首就是答案。
参考: [LeetCode解题报告] 滑动窗口最大值
package play_with_cangjie.lc.lc239
import std.collection.*
/**
* Deque双端队列,基于线性表实现,可以实现O(1)随机访问
因此这里用lambda表达式的构造方法
注意使用front/back方法命名,而不是first/last(和collection冲突,返回迭代器)
实现了更Pythonic的下标访问(index∈[-n..n-1]均合法,q[0]代表front,q[-1]代表back)
*/
class Deque<T> <: Collection<T> {
var Size = 0
public prop size: Int {
get() {Size}
}
protected var head = 0
// var _arr = Array<Option<T>>(1,repeat:Option<T>.None)
protected var _arr = Array<Option<T>>(1,{_=>Option<T>.None})
public init() { }
public init(items: Collection<T>) {
if (items.size > _arr.size) { // 提前申请空间
reserve(items.size)
}
for (v in items) {
this.append(v)
}
}
public func clear() {
Size = 0
}
public func isEmpty() {
return Size == 0
}
public func reserve(capacity: Int64) {
if (capacity <= _arr.size) {
return
}
// let arr = Array<Option<T>>(capacity,repeat:Option<T>.None)
let arr = Array<Option<T>>(capacity,{_=>Option<T>.None})
for (i in 0..Size) {
arr[i] = _arr[(this.head + i) % _arr.size]
}
head = 0
_arr = arr
}
public func front():T {
if (Size == 0) {
throw Exception("deque empty!")
}
return _arr[head].getOrThrow()
}
public func back():T {
if (Size == 0) {
throw Exception("deque empty!")
}
return _arr[(head + size+ _arr.size- 1 ) % _arr.size].getOrThrow()
}
public func popleft():T {
if (Size == 0) {
throw Exception("deque empty!")
}
let v = _arr[head].getOrThrow()
head = (head + 1) % _arr.size
Size--
return v
}
public func pop():T {
if (size == 0) {
throw Exception("deque empty!")
}
let v = _arr[(head + Size+ _arr.size - 1 ) % _arr.size].getOrThrow()
Size--
return v
}
public func append(v:T) {
if (Size == _arr.size) {
reserve(Size<<1)
}
_arr[(head+Size)%_arr.size] = Option<T>.Some(v)
Size++
}
public func appendleft(v:T) {
if (Size == _arr.size) {
reserve(size<<1)
}
head = (head+_arr.size-1)%_arr.size
_arr[head] = Option<T>.Some(v)
Size++
}
public func at(i:Int64): T {
if (i< -Size || Size<=i) {
throw Exception("index out of range ${i} for size ${Size}")
}
return _arr[(head+_arr.size+(Size+i)%Size)%_arr.size].getOrThrow()
}
public operator func [](i: Int64): T {
if (i< -Size || Size<=i) {
throw Exception("index out of range ${i} for size ${Size}")
}
return _arr[(head+_arr.size+(Size+i)%Size)%_arr.size].getOrThrow()
}
public func toArray() {
return Array<T>(Size, {i=>_arr[(head+i)%_arr.size].getOrThrow()})
}
public func iterator(): Iterator<T> {
DequeIterator(this)
}
}
extend<T> Deque<T> <: ToString where T <: ToString {
public func toString() {
return toArray().toString()
}
}
public class DequeIterator<T> <: Iterator<T> {
var cur = 0
var data = Option<Deque<T>>.None
init(data: Deque<T>) {
this.data = data
cur = 0
}
public func next(): Option<T> {
let dq = data.getOrThrow()
if (cur >= dq.size) {
return Option<T>.None
}
let ret = dq._arr[(dq.head + cur)%dq._arr.size]
cur++
return ret
}
// public func iterator(): Iterator<T>{
// return this
// }
}
public class Solution {
public func maxSlidingWindow(nums: Array<Int64>, k: Int64): Array<Int64> {
let q = Deque<Int64>([])
let ans = ArrayList<Int64>()
for (i in 0..nums.size) {
let v = nums[i]
while (!q.isEmpty() && i-q[0]>=k) {
q.popleft()
}
while(!q.isEmpty() && v >= nums[q[-1]]) {
q.pop()
}
q.append(i)
if (i>=k-1){
ans.append(nums[q[0]])
}
}
return ans.toArray()
}
}
三、其他
- 单调队列和单调栈是强力优化,就是不容易想到。