窗口内最值问题的更新结构

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.问题本身

范围达标,缩小范围必达标
范围不达标,扩大范围必不达标

把范围和问题本身建立单调性

滑动窗口,或首尾指针法
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值