zoukankan      html  css  js  c++  java
  • 数据结构队列、双端队列、队列系算法题解析

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

    一、数据结构:队列

      队列和栈类似,不同的是队列是先进先出 (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 在原有队列的基础上扩充了:队头、队尾都可以进队出队,它的数据结构如下:

    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"
    解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

    说明:

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

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

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

    画图理解:

    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 即可

    画图帮助理解一下:

    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
    }
  • 相关阅读:
    爱国论
    windows cmd: 打开windows系统程序或服务的常见命令
    Windows最常用的几个网络CMD命令总结
    什么是DNS劫持和DNS污染?
    windows cmd: 增强windows命令行
    NAT
    电信光纤猫 f412超级密码
    jvm之java类加载机制和类加载器(ClassLoader)的详解
    经典中的经典算法 动态规划(详细解释,从入门到实践,逐步讲解)
    首次适应算法、最佳适应算法和最差适应算法
  • 原文地址:https://www.cnblogs.com/goloving/p/13453991.html
Copyright © 2011-2022 走看看