package WindowsAndStack
import (
"fmt"
"testing"
)
/*
滑动窗口是什么?
滑动窗口是一种想象出来的数据结构
滑动窗口右左边界L和右边界R
在数组或者字符串或者一个序列上,记为S,窗口就是S[L,R]这一部分
L往右滑意味着一个样本出了窗口,R往右滑意味着一个样本进了窗口
L和R都只能往右滑
[ 3, 5, 2, 7, 1, 6 ]
||
LR
窗口最开始停留在数组的最左侧
1.R 向右移动 R++ 新数从右侧进入
2.L 向右移动 L++ 旧数从左侧出去
3.L <= R 任何的时候,L <= R
想知道窗口内的某个最值,不适用遍历
单调双端队列(双向链表),不代表窗口,代表窗口内的一种更新情况
保证单调双端队列从头部到尾部是由大到小的
------------------------------
3
0
------------------------------
头 大-->小 尾
0位置3 进来
R++
3弹出,1位置5进来
------------------------------
5
1
------------------------------
R++ 2位置2进来
------------------------------
5 2
1 2
------------------------------
R++ 2 弹出, 5弹出, 3位置7进来
------------------------------
7
3
------------------------------
R++
------------------------------
7 1
3 4
------------------------------
R++ 1弹出
------------------------------
7 6
3 5
------------------------------
某一时刻,双端队列头部就是最大值
*/
type DoubleList struct {
Elem []interface{}
len int
}
func NewDoubleList() *DoubleList {
return &DoubleList{
Elem: make([]interface{},0),
len: 0,
}
}
func (d *DoubleList) AddFirst(value interface{}) {
d.Elem = append([]interface{}{value},d.Elem...)
d.len++
}
func (d *DoubleList) PollFirst() interface{} {
res := d.Elem[0]
d.Elem = d.Elem[1:]
d.len--
return res
}
func (d *DoubleList) PeekFirst() interface{} {
return d.Elem[0]
}
func (d *DoubleList) PeekLast() interface{} {
return d.Elem[len(d.Elem)-1]
}
func (d *DoubleList) AddLast(value interface{}) {
d.Elem = append(d.Elem,value)
d.len++
}
func (d *DoubleList) PollLast() interface{} {
res := d.Elem[len(d.Elem)-1]
d.Elem = d.Elem[:len(d.Elem)-1]
d.len--
return res
}
func (d *DoubleList)IsEmpty() bool {
return d.len == 0
}
func TestDoubleList(t *testing.T) {
dl := NewDoubleList()
dl.AddFirst(100)
dl.AddLast(1000)
t.Log(dl.Elem)
t.Log(dl.PollFirst())
t.Log(dl.PollFirst())
t.Log(dl.Elem)
dl.AddFirst(100)
dl.AddLast(1000)
t.Log(dl.PollLast())
t.Log(dl.PollLast())
t.Log(dl.Elem)
dl.AddFirst(100)
dl.AddLast(1000)
t.Log(dl.PollFirst())
t.Log(dl.Elem)
t.Log(dl.PollLast())
t.Log(dl.Elem)
}
/*
滑动窗口能做什么?
滑动窗口,首尾指针灯技巧,说白了是一种求解问题的流程设计
*/
/*
滑动窗口内最大值和最小值的更新结构
窗口不管L还是R滑动之后,都会让窗口呈现新状况,
如何能够更快地 得到窗口当前状况下的最大值和最小值?
最后[平均下来]复杂度能做到O(1) 任何一个i位置最多进双端队列1次,最多出双端队列1次
利用单调双端队列!
*/
/*
题目一
假设一个固定大小为W的窗口,依次滑过arr,
返回每一次滑出窗框的最大值
例如: arr = [4,3,5,4,3,3,6,7], W = 3
返回: [5,5,5,4,6,7]
*/
func getMaxWindow(arr []int, w int) []int {
if arr == nil || w < 1 || len(arr) < w {
return nil
}
// 其中放的是位置,arr[位置],可以判断是否过期 头 -> 尾 大 -> 小
qmax := NewDoubleList()
res := make([]int,len(arr)-w +1)
index := 0
for R := 0; R < len(arr); R++ {
for !qmax.IsEmpty() && arr[qmax.PeekLast().(int)] <= arr[R] {
qmax.PollLast()
}
qmax.AddLast(R)
if qmax.PeekFirst().(int) == R - w { //是过期下标
qmax.PollFirst()
}
if R >= w - 1 { // 形成窗口再收集答案
res[index] = arr[qmax.PeekFirst().(int)]
index++
}
}
return res
}
func TestGetMaxWindow(t *testing.T) {
fmt.Println(getMaxWindow([]int{4,3,5,4,3,3,6,7},3))
}
/*
题目二
给定一个整型数组arr,和一个整数num
某个arr中的子数组sub,如果想达标,必须满足:
sub中最大值 - sub中最小值 <= num
返回arr中达标子数组的数量
暴力 O(N^3)
窗口 O(N)
*/
/*
arr [L..R]达标
L...R 之间任何一个子数组都达标
max(arr[L...R]) - min(arr[L...R])
*/
func getNum(arr []int, num int) int {
if arr == nil || len(arr) == 0 {
return 0
}
qmin := NewDoubleList()
qmax := NewDoubleList()
L, R, res := 0, 0, 0 // [L..R) - > [0,0) 窗口内无数 [1,1)
for L < len(arr) { // L 是开头位置,尝试每一个开头
// 如果此时窗口的开头是L,下面的for工作: R向右扩到违规位置
for R < len(arr) { // R是最后一个达标位置的下一个
for !qmin.IsEmpty() && arr[qmin.PeekLast().(int)] >= arr[R] {
qmin.PollLast()
}
qmin.AddLast(R)
for !qmax.IsEmpty() && arr[qmax.PeekLast().(int)] <= arr[R] {
qmax.PollLast()
}
qmax.AddLast(R)
if arr[qmax.PeekFirst().(int)] - arr[qmin.PeekFirst().(int)] > num {
break
}
R++
}
res += R - L
if qmin.PeekFirst().(int) == L {
qmin.PollFirst()
}
if qmax.PeekFirst().(int) == L {
qmax.PollFirst()
}
L++
}
return res
}
func TestGetNum(t *testing.T) {
arr := []int{3,6,5,6,8,34,90,9}
t.Log(getNum(arr,10))
}
/*
优化:
1.数据状况
2.问题本身
范围达标,缩小范围必达标
范围不达标,扩大范围必不达标
把范围和问题本身建立单调性
滑动窗口,或首尾指针法
*/
窗口内最值问题的更新结构
最新推荐文章于 2024-01-28 22:37:20 发布