队列这种数据结构,前端需要了解的队列结构主要有:双端队列、滑动窗口,它们都是算法中是比较常用的数据结构。

一、数据结构:队列

队列和栈类似,不同的是队列是先进先出 (FIFO) 原则的有序集合,它的结构类似如下:

数据结构队列、双端队列、队列系算法题解析_双端队列

常见队列的操作有:​​enqueue(e)​​​ 进队、 ​​dequeue()​​​ 出队、 ​​isEmpty()​​​是否是空队、 ​​front()​​​ 获取队头元素、​​clear()​​​ 清空队,以及 ​​size()​​获取队列长度。


function Queue() {   let items = []   this.enqueue = function(e) {     items.push(e)   }   this.dequeue = function() {     return items.shift()   }   this.isEmpty = function() {     return items.length === 0   }   this.front = function() {     return items[0]   }   this.clear = function() {      items = []    }   this.size = function() {     return items.length   } }


查找:从对头开始查找,从时间复杂度为 O(n)

插入或删除:进栈与出栈的时间复杂度为 O(1)

二、双端队列(Deque)

1、什么是 Deque

Deque 在原有队列的基础上扩充了:队头、队尾都可以进队出队,它的数据结构如下:数据结构队列、双端队列、队列系算法题解析_双端队列_02


function Deque() {   let items = []   this.addFirst = function(e) {     items.unshift(e)   }   this.removeFirst = function() {     return items.shift()   }   this.addLast = function(e) {     items.push(e)   }   this.removeLast = function() {     return items.pop()   }   this.isEmpty = function() {     return items.length === 0   }   this.front = function() {     return items[0]   }   this.clear = function() {      items = []    }   this.size = function() {     return items.length   } }


三、双端队列问题

给定一个字符串,逐个翻转字符串中的每个单词。


示例1: 输入: "the sky is blue" 输出: "blue is sky the"  示例2: 输入: "  hello world!  " 输出: "world! hello" 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。  示例3: 输入: "a good   example" 输出: "example good a" 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。


说明:


  • 无空格字符构成一个单词。
  • 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
  • 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

解题思路:使用双端队列解题


  • 首先去除字符串左右空格
  • 逐个读取字符串中的每个单词,依次放入双端队列的对头
  • 再将队列转换成字符串输出(已空格为分隔符)

画图理解:

数据结构队列、双端队列、队列系算法题解析_数组_03


数据结构队列、双端队列、队列系算法题解析_滑动窗口_04


数据结构队列、双端队列、队列系算法题解析_滑动窗口_05


var reverseWords = function(s) {     let left = 0     let right = s.length - 1     let queue = []     let word = ''     while (s.charAt(left) === ' ') left ++     while (s.charAt(right) === ' ') right --     while (left <= right) {         let char = s.charAt(left)         if (char === ' ' && word) {             queue.unshift(word)             word = ''         } else if (char !== ' '){             word += char         }         left++     }     queue.unshift(word)     return queue.join(' ') };


四、滑动窗口

1、什么是滑动窗口

这是队列的另一个重要应用。顾名思义,滑动窗口就是一个运行在一个大数组上的子列表,该数组是一个底层元素集合。假设有数组 [a b c d e f g h ],一个大小为 3 的 滑动窗口在其上滑动,则有:


[a b c]   [b c d]     [c d e]       [d e f]         [e f g]           [f g h]


一般情况下就是使用这个窗口在数组的 合法区间 内进行滑动,同时 动态地 记录一些有用的数据,很多情况下,能够极大地提高算法地效率。

下面看一道经典的滑动窗口问题:给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。


示例 1: 输入: "abcabcbb" 输出: 3  解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。  示例 2: 输入: "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。  示例 3: 输入: "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。


解题思路: 使用一个数组来维护滑动窗口

遍历字符串,判断字符是否在滑动窗口数组里


  • 不在则 ​​push​​ 进数组
  • 在则删除滑动窗口数组里相同字符及相同字符前的字符,然后将当前字符 ​​push​​ 进数组
  • 然后将 ​​max​​ 更新为当前最长子串的长度

遍历完,返回 ​​max​​ 即可

画图帮助理解一下:

数据结构队列、双端队列、队列系算法题解析_双端队列_06


var lengthOfLongestSubstring = function(s) {     let arr = [], max = 0     for(let i = 0; i < s.length; i++) {         let index = arr.indexOf(s[i])         if(index !== -1) {             arr.splice(0, index+1);         }         arr.push(s.charAt(i))         max = Math.max(arr.length, max)      }     return max };


时间复杂度:O(n2), 其中 ​​arr.indexOf()​​​ 时间复杂度为 O(n) ,​​arr.splice(0, index+1)​​ 的时间复杂度也为 O(n)

2、滑动窗口最大值问题

给定一个数组 ​​nums​​​ 和滑动窗口的大小 ​​k​​,请找出所有滑动窗口里的最大值。


示例: 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 输出: [3,3,5,5,6,7]   解释: 滑动窗口的位置                最大值 [1  3  -1] -3  5  3  6  7       3   1 [3  -1  -3] 5  3  6  7       3  1  3 [-1  -3  5] 3  6  7       5   1  3  -1 [-3  5  3] 6  7       5   1  3  -1  -3 [5  3  6] 7       6   1  3  -1  -3  5 [3  6  7]      7


你可以假设 ​​k​​ 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。

解题思路:


var maxSlidingWindow = function(nums, k) {   let max = [];  //存放最大值   let _len = nums.length - k   for(let i=0; i <= _len; i++){     const _ = nums.slice(i, i + k)     if(_.length === k){       max.push(Math.max(..._))     }   }   return max }