数据结构:窗口最大值
最容易想到的算法是,遍历每个元素,还是在每个元素和后面两个元素进行比较获取最大值,这样一种两级循环的方式;没有问题。但是这样空间的复杂度就不是O(N)了,而是O(N*WindowCount)。
实现O(N)的一种方式就是:利用双端列表来实现。
一个源,数组,从入口参数中传入,一个变量,窗口Count;
从源中取出一个数据,判断当前列表是否为空,为空直接让如;如果不为空,则从队尾中取出元素进行比较。如果说当前元素比队尾元素大,则队尾元素出队列,再取出新的队尾元素和当前元素比较,直到发现了一个队尾元素比当前元素大或者队列为空。
注意,数据结构通常的思路:考虑清楚退出条件;退出条件主要有两种:递归(不断地出栈、出队)的判断,设置条件,一个业务谓词(逻辑判断,大,小,数量达到某个值);第二种就是 结构谓词(队列为空)。
另外上面描述的逻辑中有一个常见的算法概念:空降高权。什么叫空降最大,就是待处理的元素就是空降元素,高权就是无论该空降元素是大是小,一定是要放入到队列中的;因为如果它小,那么它在未来是可能成为最大的,你要留着它,如果它大,那么就要把队列中现有,从队尾倒撸,没它大的都干掉,然后在放入到队尾。
只要处理的次数达到了windowsCount,就需要每次都从队头中取出元素,记做当前窗口的最大值(只要第一次处理次数达到了窗口数,以后每次处理一个都意味着一个窗口满了)
这里还有一个判断,就是如果一个元素很大,但是他不能一直在队头,如果窗口已经划过就不能在队头呆着,可以说是过期了,怎么判断?就看每次放入一个数据后,都check一下队列的数量,达到了窗口数量,就意味着队头元素已经过期(新来的元素一定是要挤掉这个元素的),于是将该队头元素删除。
实现上,队列(双端)采用的是LinkedList,之前采用ArrayList,为了实现循环删除队队尾,我需要单独写一个函数,因为java里面的ArrayList是不能直接遍历删除元素,需要转化为Iterator,然后用这个Iterator来动态(遍历)删除。
另外,通过这个例子,我觉得做这类问题,有一点非常重要:就是有一种建立模型的意识,或者讲是“翻译”的意识,比如窗口最大值,其实翻译过来就是:每三个值中选出最大的;注意只关心最大值,到此算是翻译到家了;再分析数据,数据分为两种,一种是特殊数据,首先就是这个最大值,在数据接结构里面,最大值就是要放在特殊位置,队头或者队尾,以便于取出;另外还需要一个数组来存放每次被识别出来的最大值;另外一种其他数据,从要求来看,其他数据无所谓,直接出队列即可;最后梳理数据的生命周期,从特殊数据分析,从放入到超时,超时处理为从队列弹出(弹出上面没有分析到,在这个环节被分析出来);对于普通数据,从放入,到比较后发现小,弹出完事。
数据结构分析到此结束,下面就是考虑技术实现了。比如在此例中,使用LinkedList最为合适,因为是队列形式,还提供了队头队尾的poll;
源码:
1 public static void main(String[] args) { 2 int[] values = { 3, 2, 8, 9, 9, 0, 2, 1, 5, 5, 1 }; 3 WindowMaxValueQueue queue = new WindowMaxValueQueue(); 4 queue.run(values, 3); 5 } 6 7 public void run(int[] values, int windowCount) { 8 int counter = 0; 9 LinkedList<Integer> lst = new LinkedList<Integer>(); 10 List<Integer> maxValues = new ArrayList<Integer>(); 11 for (int value : values) { 12 logger.info("待处理元素: {}", value); 13 // 队列中没有元素直接放入list中 14 if (lst.size() == 0) { 15 logger.info("队列为空,直接放入元素: {}", value); 16 lst.add(value); 17 } else { 18 while (lst.size() > 0) { 19 int tail = lst.peekLast(); 20 if (tail <= value) { 21 lst.removeLast(); 22 } else { 23 break; 24 } 25 } 26 lst.add(value); 27 28 String elements = ""; 29 for (int i = 0; i < lst.size(); i++) { 30 elements = elements + lst.get(i) + "; "; 31 } 32 33 logger.info("添加元素: {}; 队列元素列表: {}", value, elements); 34 } 35 36 counter++; 37 // 只要索引大于了窗口,每个加入的元素都会产生一个窗口最大值 38 if (counter >= windowCount) { 39 int head = lst.get(0); 40 logger.info("窗口最大值: {}", head); 41 maxValues.add(head); 42 } 43 // 如果队列个数满足了三个,头元素删除 44 if (lst.size() == 3) { 45 logger.info("窗口数量达到了三个,头元素: {}进行删除", lst.get(0)); 46 lst.remove(0); 47 } 48 } 49 50 int index = 1; 51 for (int maxValue : maxValues) { 52 System.out.println(index++ + ". value: " + maxValue); 53 } 54 } 55 }